//! Runtime composition root.
//!
//! Wires every component handle, owns the actor join-handles, and aggregates
//! per-component health for the `/health` endpoint. Per-component construction
//! and channel wiring lands in the per-component implementation tasks
//! (AZ-641 onwards); today's bootstrap exposes the aggregation surface only.

use std::path::Path;

use shared::config::Config;
use shared::health::{AggregatedHealth, ComponentHealth};

/// Components named in `/_docs/02_document/components/`. The list drives both
/// the bootstrap health payload and the eventual per-component wiring.
pub const COMPONENT_NAMES: &[&str] = &[
    "frame_ingest",
    "detection_client",
    "movement_detector",
    "semantic_analyzer",
    "vlm_client",
    "scan_controller",
    "mapobjects_store",
    "gimbal_controller",
    "operator_bridge",
    "mission_executor",
    "mavlink_layer",
    "mission_client",
    "telemetry_stream",
];

/// Owns the configuration and the eventual actor topology.
pub struct Runtime {
    config: Config,
}

impl Runtime {
    pub fn new(config: Config) -> Self {
        Self { config }
    }

    // Public for future per-component wiring (AZ-641+).
    #[allow(dead_code)]
    pub fn config(&self) -> &Config {
        &self.config
    }

    /// Aggregated health snapshot used by the `/health` endpoint.
    ///
    /// While the per-component handles are not yet wired (bootstrap phase),
    /// the snapshot reports every component as `Disabled` so the endpoint shape
    /// already matches the contract in `containerization.md §7`.
    pub fn health_snapshot(&self) -> AggregatedHealth {
        // Every component is `Disabled` during bootstrap. Per-component
        // wiring (AZ-641+) will return real health levels as actors come up.
        // VLM stays `Disabled` whenever `config.vlm.enabled = false` even after
        // wiring.
        let components = COMPONENT_NAMES
            .iter()
            .map(|name| ComponentHealth::disabled(name))
            .collect();
        let _ = self.config.vlm.enabled; // keeps the field used until AZ-672 wiring lands
        AggregatedHealth::aggregate(components)
    }
}

/// Create the persistent state subtree under `state_dir`.
/// Subdirectories per `data_model.md §6` and `containerization.md §3`.
pub fn ensure_state_directories(state_dir: &str) -> std::io::Result<()> {
    let root = Path::new(state_dir);
    for sub in ["mapobjects", "audit", "pending_pushes"] {
        std::fs::create_dir_all(root.join(sub))?;
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::sync::atomic::{AtomicUsize, Ordering};

    static SEQ: AtomicUsize = AtomicUsize::new(0);

    fn tmp_state_dir() -> std::path::PathBuf {
        let n = SEQ.fetch_add(1, Ordering::SeqCst);
        let pid = std::process::id();
        std::env::temp_dir().join(format!("autopilot-test-state-{pid}-{n}"))
    }

    #[test]
    fn ensure_state_directories_creates_subdirs() {
        // Arrange
        let dir = tmp_state_dir();
        let _ = std::fs::remove_dir_all(&dir);

        // Act
        ensure_state_directories(dir.to_str().unwrap()).expect("dirs created");

        // Assert
        for sub in ["mapobjects", "audit", "pending_pushes"] {
            let path = dir.join(sub);
            assert!(path.is_dir(), "expected dir {path:?} to exist");
        }

        let _ = std::fs::remove_dir_all(&dir);
    }
}
