//! Cookbook: message-window streaming
//!
//! Demonstrates the [`MessageWindowsIter`] API for iterating over
//! `UNH..UNT` message windows without buffering the entire interchange.
//!
//! # Key APIs
//!
//! - [`from_bytes_windows`] — byte-slice source, produces owned windows
//! - [`message_windows_from_reader`] — reader source, lazy per-window I/O
//! - [`deserialize_messages_from_reader`] — reader source + typed deserialisation
//!
//! All three variants skip envelope segments (`UNB`, `UNZ`, `UNG`, `UNE`)
//! automatically and yield only the `UNH..UNT` windows.

use edifact_rs::{
    EdifactDeserialize, EdifactSerialize, from_bytes_windows, message_windows_from_reader,
};

// ── 1. Manual approach: inspect windows as raw segment slices ─────────────────

fn count_messages_in_interchange(input: &[u8]) -> usize {
    from_bytes_windows(input).filter_map(Result::ok).count()
}

// ── 2. Typed message struct via derive macros ─────────────────────────────────

/// A minimal ORDERS message representation.
///
/// In a real codebase this would be generated from the EDIFACT directory.
#[cfg(feature = "derive")]
#[derive(Debug, EdifactDeserialize, EdifactSerialize, PartialEq)]
#[edifact(segment = "BGM")]
struct BgmSegment {
    #[edifact(element = 0)]
    doc_code: String,
    #[edifact(element = 1)]
    doc_id: Option<String>,
}

#[cfg(feature = "derive")]
#[derive(Debug, EdifactDeserialize, PartialEq)]
struct OrdersMessage {
    bgm: Option<BgmSegment>,
}

// ── 3. Typed streaming over a reader ─────────────────────────────────────────

#[cfg(feature = "derive")]
fn collect_order_ids(input: &[u8]) -> Vec<Option<String>> {
    use std::io::Cursor;

    // reader variant — identical ergonomics, lazy I/O
    let reader = Cursor::new(input);
    message_windows_from_reader(reader)
        .filter_map(Result::ok)
        .map(|window| {
            // Each window is an OwnedMessageWindow; deserialise directly from its segments.
            OrdersMessage::edifact_deserialize_owned(&window.segments)
                .ok()
                .and_then(|m| m.bgm.into_iter().next())
                .and_then(|bgm| bgm.doc_id)
        })
        .collect()
}

// ── 4. Error propagation — missing UNT is surfaced immediately ────────────────

fn demonstrate_error_propagation() {
    // Interchange where the second message has no UNT — window iterator errors.
    let broken = b"UNB+UNOA:1+S+R+200101:0900+1'\
                   UNH+1+ORDERS:D:96A:UN'BGM+220+OK+9'UNT+3+1'\
                   UNH+2+ORDERS:D:96A:UN'BGM+220+BROKEN+9'";
    // Collecting all windows: the unclosed window is surfaced as an error on
    // the final iteration, after the first complete window has already been yielded.
    let windows: Vec<_> = from_bytes_windows(broken).collect();
    // The first window is complete, and the second item is the EOF error.
    assert_eq!(windows.len(), 2);
    assert!(matches!(
        windows[1],
        Err(edifact_rs::EdifactError::UnexpectedEof { .. })
    ));
    println!(
        "Received {} complete window(s) before end-of-stream",
        windows.iter().filter(|result| result.is_ok()).count()
    );

    // A UNH-while-in-flight scenario (no UNT before next UNH) is an error:
    let double_unh = b"UNH+1+ORDERS:D:96A:UN'BGM+220+X+9'\
                       UNH+2+ORDERS:D:96A:UN'BGM+220+Y+9'UNT+3+2'";
    let results: Vec<_> = from_bytes_windows(double_unh).collect();
    assert!(
        results.iter().any(|r| r.is_err()),
        "expected an error for double-UNH"
    );
    println!("Double-UNH correctly produces an error");
}

fn main() {
    let interchange = b"\
        UNB+UNOA:1+SENDER+RECEIVER+200101:0900+1'\
        UNH+1+ORDERS:D:96A:UN'\
        BGM+220+PO-001+9'\
        UNT+3+1'\
        UNH+2+ORDERS:D:96A:UN'\
        BGM+220+PO-002+9'\
        UNT+3+2'\
        UNZ+2+1'";

    // ── raw window iteration ──────────────────────────────────────────────────
    let count = count_messages_in_interchange(interchange);
    println!("Interchange contains {count} message(s)");
    assert_eq!(count, 2);

    // Inspect each window directly
    for (i, window) in from_bytes_windows(interchange)
        .enumerate()
        .map(|(i, r)| (i, r.unwrap()))
    {
        let msg_type = window.message_type.as_deref().unwrap_or("?");
        println!(
            "Window {}: type={msg_type}, segments={}",
            i + 1,
            window.segments.len()
        );
    }

    // ── typed streaming ───────────────────────────────────────────────────────
    #[cfg(feature = "derive")]
    {
        let order_ids = collect_order_ids(interchange);
        println!("Order IDs: {order_ids:?}");
        assert_eq!(order_ids.len(), 2);
    }

    demonstrate_error_propagation();
    println!("All message-window streaming examples passed.");
}
