//! Example usage of the #[derive(Node)] macro
//!
//! This example demonstrates how to use the Node derive macro to automatically
//! generate the `run()` function, eliminating boilerplate for node setup.

use mecha10::prelude::*;

// ============================================================================
// Example 1: Simple Node (Single Instance)
// ============================================================================

/// Simple camera driver with auto-generated run() function
#[derive(Debug, Node)]
#[node(
    name = "camera_simple",
    config_env = "CAMERA_CONFIG",
    config_path = "config/drivers/camera.yaml",
    env_prefix = "CAMERA"
)]
pub struct SimpleCameraNode {
    config: SimpleCameraConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct SimpleCameraConfig {
    pub fps: f32,
    pub width: u32,
    pub height: u32,
}

#[async_trait]
impl NodeImpl for SimpleCameraNode {
    type Config = SimpleCameraConfig;

    async fn init(config: Self::Config) -> Result<Self> {
        info!("Initializing simple camera at {} fps", config.fps);
        Ok(Self { config })
    }

    async fn run(&mut self, ctx: &Context) -> Result<()> {
        let mut interval = interval_at_hz(self.config.fps);

        loop {
            interval.tick().await;
            // Capture and publish images
            debug!("Camera tick");
        }
    }
}

// The #[derive(Node)] macro automatically generates:
//
// pub async fn run() -> ::mecha10::Result<()> {
//     let config = ::mecha10::config::load_config_with_fallback(
//         "CAMERA_CONFIG",
//         "config/drivers/camera.yaml",
//         "CAMERA",
//         Some(SimpleCameraConfig::default()),
//     )?;
//     let ctx = ::mecha10::context::Context::new("camera_simple").await?;
//     ::mecha10::node::run_node::<SimpleCameraNode>(
//         config,
//         ctx,
//         ::mecha10::node::HealthReportingConfig::default()
//     ).await
// }

// ============================================================================
// Example 2: Multi-Instance Node
// ============================================================================

/// Camera driver with multi-instance support
#[derive(Debug, Node)]
#[node(
    name = "camera_multi",
    config_env = "CAMERA_CONFIG",
    config_path = "config/drivers/camera.yaml",
    env_prefix = "CAMERA",
    instance_env = "CAMERA_INSTANCE"  // Enable multi-instance support
)]
pub struct MultiCameraNode {
    config: CameraConfig,
    frame_counter: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct CameraConfig {
    pub fps: f32,
    pub width: u32,
    pub height: u32,
}

#[async_trait]
impl NodeImpl for MultiCameraNode {
    type Config = CameraConfig;

    async fn init(config: Self::Config) -> Result<Self> {
        info!("Initializing multi-instance camera at {} fps", config.fps);
        Ok(Self {
            config,
            frame_counter: 0,
        })
    }

    async fn run(&mut self, ctx: &Context) -> Result<()> {
        let mut interval = interval_at_hz(self.config.fps);

        loop {
            interval.tick().await;

            // When running with instance name, topics are automatically scoped
            // Example: CAMERA_INSTANCE=left creates "/camera/rgb/left"
            self.frame_counter += 1;

            if self.frame_counter % 100 == 0 {
                debug!("Published {} frames", self.frame_counter);
            }
        }
    }
}

// With instance_env, the macro generates:
//
// pub async fn run() -> ::mecha10::Result<()> {
//     let config = ::mecha10::config::load_config_with_fallback(...)?;
//     let mut ctx = ::mecha10::context::Context::new("camera_multi").await?;
//
//     // Check for instance name
//     if let Ok(instance) = std::env::var("CAMERA_INSTANCE") {
//         ctx = ctx.with_instance(&instance);
//         info!("Running node 'camera_multi' with instance '{}'", instance);
//     }
//
//     ::mecha10::node::run_node::<MultiCameraNode>(...).await
// }

// ============================================================================
// Example 3: Node with Minimal Attributes
// ============================================================================

/// Lidar driver with minimal configuration
/// The macro will use sensible defaults for missing attributes
#[derive(Debug, Node)]
#[node(name = "lidar")] // Only node name is required
pub struct LidarNode {
    config: LidarConfig,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct LidarConfig {
    pub scan_rate_hz: f32,
}

#[async_trait]
impl NodeImpl for LidarNode {
    type Config = LidarConfig;

    async fn init(config: Self::Config) -> Result<Self> {
        info!("Initializing lidar at {} Hz", config.scan_rate_hz);
        Ok(Self { config })
    }

    async fn run(&mut self, ctx: &Context) -> Result<()> {
        let mut interval = interval_at_hz(self.config.scan_rate_hz);

        loop {
            interval.tick().await;
            // Scan and publish point cloud
        }
    }
}

// With minimal attributes, the macro uses defaults:
// - config_env: "LIDAR_CONFIG" (NAME + "_CONFIG")
// - config_path: "config/lidar.yaml" ("config/" + name + ".yaml")
// - env_prefix: "LIDAR" (NAME in uppercase)

// ============================================================================
// Comparison: Before and After
// ============================================================================

// BEFORE (manual boilerplate):
//
// pub async fn run() -> Result<()> {
//     init_logging();
//     info!("Starting camera driver");
//
//     let config = load_config_with_fallback(
//         "CAMERA_CONFIG",
//         "config/drivers/camera.yaml",
//         "CAMERA",
//         Some(CameraConfig::default())
//     )?;
//
//     let mut ctx = Context::new("camera_fake").await?;
//
//     if let Ok(instance) = std::env::var("CAMERA_INSTANCE") {
//         ctx = ctx.with_instance(&instance);
//         info!("Running node with instance '{}'", instance);
//     }
//
//     run_node::<CameraFakeNode>(
//         config,
//         ctx,
//         HealthReportingConfig::default()
//     ).await
// }
//
// AFTER (using #[derive(Node)]):
//
// #[derive(Debug, Node)]
// #[node(
//     name = "camera_fake",
//     instance_env = "CAMERA_INSTANCE"
// )]
// pub struct CameraFakeNode { ... }
//
// // That's it! The run() function is auto-generated.

fn main() {
    println!("Node macro examples:");
    println!();
    println!("1. Simple node:");
    println!("   cargo run --example simple_camera");
    println!();
    println!("2. Multi-instance node:");
    println!("   CAMERA_INSTANCE=left cargo run --example multi_camera");
    println!("   CAMERA_INSTANCE=right cargo run --example multi_camera");
    println!();
    println!("3. Minimal node:");
    println!("   cargo run --example lidar");
    println!();
    println!("Benefits of #[derive(Node)]:");
    println!("- Eliminates 15-20 lines of boilerplate per node");
    println!("- Standardizes node setup across the codebase");
    println!("- Reduces copy-paste errors");
    println!("- Makes multi-instance support trivial to add");
    println!("- Automatic config loading with fallback chain");
    println!("- Consistent health reporting configuration");
}
