//! @docs ARCHITECTURE:TelemetryBridge
//!
//! ### AI Assist Note
//! **Swarm Pulse (High-Speed Telemetry)**: Orchestrates the
//! real-time aggregation of agent states into a **MessagePack (MsgPack)**
//! binary pulse for sub-millisecond visualization. Features a
//! **100ms Pulse Loop**: periodically broadcasts `SwarmPulse` objects
//! containing node status, battery (budget), and signal (heartbeat)
//! metrics. Implements **Binary Pulse Consolidation**: encoding
//! happens in the WebSocket handler to minimize double-encoding
//! overhead across multiple concurrent client visualizers (PULS-01).
//!
//! ### 🔍 Debugging & Observability
//! - **Failure Path**: High CPU/Memory overhead during pulse
//!   generation for large swarms (>100 agents), MessagePack
//!   deserialization failures in the frontend, or stale signal
//!   metrics due to heartbeat latency spikes.
//! - **Trace Scope**: `server-rs::telemetry::pulse`

use crate::state::AppState;
use crate::telemetry::pulse_types::{PulseConnection, PulseNode, SwarmPulse};
use std::collections::HashSet;
use std::sync::Arc;
use tokio::time::{interval, Duration};

/// Launches the high-speed pulse loop (100ms interval).
pub async fn spawn_pulse_loop(
    state: Arc<AppState>,
    mut shutdown_rx: tokio::sync::watch::Receiver<bool>,
) {
    let mut interval = interval(Duration::from_millis(100));

    tracing::info!("💓 [Telemetry] Swarm Pulse Loop (MsgPack) started (100ms interval)");

    loop {
        tokio::select! {
                _ = shutdown_rx.changed() => {
                    if *shutdown_rx.borrow() {
                        tracing::info!("🛑 Swarm Pulse Loop shutting down gracefully.");
                        break;
                    }
                }
                _ = interval.tick() => {

            // 1. Build the pulse from the current registry state
            let timestamp = chrono::Utc::now().timestamp_millis() as u64;
            let mut pulse = SwarmPulse::new(timestamp);
            let mut active_missions = HashSet::new();

            // 2. Map Agents to Nodes
            for entry in state.registry.agents.iter() {
                let agent = entry.value();

                // Map status string to u8 for the pulse
                // Agent status: (idle | running | throttled | failed)
                // Pulse status: 0: idle, 1: busy, 2: error, 3: degraded
                let status = match agent.health.status.as_str() {
                    "running" | "active" | "thinking" | "working" => 1,
                    "failed" => 2,
                    "throttled" => 3,
                    _ => 0, // idle
                };

                // Map battery (budget used percentage)
                let battery = if agent.economics.budget_usd > 0.0 {
                    let remaining = (agent.economics.budget_usd - agent.economics.cost_usd).max(0.0);
                    ((remaining / agent.economics.budget_usd) * 100.0) as u8
                } else {
                    100
                };

                // Calculate signal based on heartbeat latency
                let signal = if let Some(last_heartbeat) = agent.health.heartbeat_at {
                    let latency = (chrono::Utc::now() - last_heartbeat).num_seconds();
                    if latency < 5 {
                        100
                    } else if latency < 15 {
                        70
                    } else if latency < 30 {
                        40
                    } else {
                        10
                    }
                } else {
                    100
                };

                pulse.nodes.push(PulseNode {
                    id: agent.identity.id.clone(),
                    x: 0.0, // Layout handled by frontend ForceGraph
                    y: 0.0,
                    status,
                    battery,
                    signal,
                    progress: 0.0,
                });

                // 3. Map Connections (Active Mission Relationships)
                if let Some(mission) = &agent.state.active_mission {
                    if let Some(mission_id) = mission
                        .get("id")
                        .and_then(|v: &serde_json::Value| v.as_str())
                    {
                        active_missions.insert(mission_id.to_string());
                        pulse.edges.push(PulseConnection {
                            source: agent.identity.id.clone(),
                            target: "mission-hub".to_string(), // Always anchor to hub for high-speed pulse
                        });
                    }
                }
            }

            // 4. Synthesize Mission Nodes (Central Anchors)
            // This ensures the UI visualizer has valid targets for mission edges.
            for mission_id in active_missions {
                pulse.nodes.push(PulseNode {
                    id: mission_id,
                    x: 0.0,
                    y: 0.0,
                    status: 4, // Status 4 designated for 'Mission Hub'
                    battery: 100,
                    signal: 100,
                    progress: 0.0,
                });
            }

            // 5. Broadcast the pulse
            let node_count = pulse.nodes.len();
            let edge_count = pulse.edges.len();

            // Periodic diagnostic logging (every ~5 seconds at 10Hz)
            static TICK_COUNT: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
            let ticks = TICK_COUNT.fetch_add(1, std::sync::atomic::Ordering::Relaxed) + 1;
            if ticks.is_multiple_of(50) {
                tracing::info!(
                    "💓 [Telemetry] Swarm Pulse: {} nodes, {} edges broadcast",
                    node_count,
                    edge_count
                );
            }

            let _ = state.comms.pulse_tx.send(Arc::new(pulse));
            }
        }
    }
}

// Metadata: [pulse]
