//! UDP aggregator CLI for receiving ESP32 CSI frames (ADR-018).
//!
//! Listens for ADR-018 binary CSI frames on a UDP socket, parses each
//! packet, and prints a one-line summary to stdout.
//!
//! Usage:
//!   cargo run -p wifi-densepose-hardware --bin aggregator -- --bind 0.0.0.0:5005

use std::net::UdpSocket;
use std::process;

use clap::Parser;
use wifi_densepose_hardware::{Esp32CsiParser, ParseError};

/// UDP aggregator for ESP32 CSI nodes (ADR-018).
#[derive(Parser)]
#[command(
    name = "aggregator",
    about = "Receive and display live CSI frames from ESP32 nodes"
)]
struct Cli {
    /// Address:port to bind the UDP listener to.
    #[arg(long, default_value = "0.0.0.0:5005")]
    bind: String,

    /// Print raw hex dump alongside parsed output.
    #[arg(long, short)]
    verbose: bool,
}

fn main() {
    let cli = Cli::parse();

    let socket = match UdpSocket::bind(&cli.bind) {
        Ok(s) => s,
        Err(e) => {
            eprintln!("Error: cannot bind to {}: {}", cli.bind, e);
            process::exit(1);
        }
    };

    eprintln!("Listening on {}...", cli.bind);

    let mut buf = [0u8; 2048];

    loop {
        let (n, src) = match socket.recv_from(&mut buf) {
            Ok(r) => r,
            Err(e) => {
                eprintln!("recv error: {}", e);
                continue;
            }
        };

        if cli.verbose {
            eprintln!("  [{} bytes from {}]", n, src);
        }

        match Esp32CsiParser::parse_frame(&buf[..n]) {
            Ok((frame, _consumed)) => {
                let mean_amp = frame.mean_amplitude();
                println!(
                    "[node:{} seq:{}] sc={} rssi={} amp={:.1}",
                    frame.metadata.node_id,
                    frame.metadata.sequence,
                    frame.subcarrier_count(),
                    frame.metadata.rssi_dbm,
                    mean_amp,
                );
            }
            // The firmware sends several packet types on this UDP port
            // (ADR-039 vitals, ADR-081 feature state, ADR-095 temporal, …)
            // alongside ADR-018 CSI frames. Those are expected, not errors —
            // this CSI-only aggregator just skips them. (RuView#517)
            Err(ParseError::NonCsiPacket { kind, .. }) => {
                if cli.verbose {
                    eprintln!("  [skipped {} packet — not a CSI frame]", kind);
                }
            }
            Err(e) => {
                if cli.verbose {
                    eprintln!("  parse error: {}", e);
                }
            }
        }
    }
}
