//! Mecha10 Procedural Macros
//!
//! This crate provides derive macros to reduce boilerplate in Mecha10 configuration structs.

use darling::FromDeriveInput;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields, Meta};

/// Derive macro for automatic config defaults
///
/// This macro eliminates the need for manual `default_*()` functions and
/// manual `Default` implementation.
///
/// # Attributes
///
/// - `#[config(default = value)]` - Specifies the default value for a field
/// - Fields without `#[config(default)]` will use `Default::default()` or `None` for Option types
///
/// # Example
///
/// ## Before (Manual approach):
///
/// ```ignore
/// #[derive(Debug, Clone, Serialize, Deserialize)]
/// pub struct CameraConfig {
///     #[serde(default = "default_fps")]
///     pub fps: f32,
///
///     #[serde(default = "default_width")]
///     pub width: u32,
///
///     pub device: Option<String>,
/// }
///
/// fn default_fps() -> f32 { 30.0 }
/// fn default_width() -> u32 { 640 }
///
/// impl Default for CameraConfig {
///     fn default() -> Self {
///         Self {
///             fps: default_fps(),
///             width: default_width(),
///             device: None,
///         }
///     }
/// }
/// ```
///
/// ## After (Using ConfigDefaults):
///
/// ```ignore
/// use mecha10_macros::ConfigDefaults;
///
/// #[derive(Debug, Clone, Serialize, Deserialize, ConfigDefaults)]
/// #[config_defaults]
/// pub struct CameraConfig {
///     #[serde(default = "CameraConfig::default_fps")]
///     #[config(default = 30.0)]
///     pub fps: f32,
///
///     #[serde(default = "CameraConfig::default_width")]
///     #[config(default = 640)]
///     pub width: u32,
///
///     pub device: Option<String>,
/// }
/// ```
///
/// This generates:
/// - Associated functions `default_fps()`, `default_width()` in the `impl` block
/// - A `Default` trait implementation
///
/// You still need the `#[serde(default = "TypeName::default_field")]` attribute,
/// but the actual default functions and Default impl are generated for you.
#[proc_macro_derive(ConfigDefaults, attributes(config))]
pub fn derive_config_defaults(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    let fields = match &input.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(fields) => &fields.named,
            _ => {
                return syn::Error::new_spanned(&input, "ConfigDefaults only supports structs with named fields")
                    .to_compile_error()
                    .into();
            }
        },
        _ => {
            return syn::Error::new_spanned(&input, "ConfigDefaults only supports structs")
                .to_compile_error()
                .into();
        }
    };

    let mut default_fns = Vec::new();
    let mut field_inits = Vec::new();

    for field in fields {
        let field_name = field.ident.as_ref().unwrap();
        let field_type = &field.ty;

        // Parse the #[config(default = value)] attribute
        let default_value = field.attrs.iter().find_map(|attr| {
            if !attr.path().is_ident("config") {
                return None;
            }

            attr.parse_args::<Meta>().ok().and_then(|meta| match meta {
                Meta::NameValue(nv) if nv.path.is_ident("default") => Some(nv.value),
                _ => None,
            })
        });

        if let Some(default_val) = default_value {
            // Generate default function
            let default_fn_name = syn::Ident::new(&format!("default_{}", field_name), field_name.span());

            default_fns.push(quote! {
                pub fn #default_fn_name() -> #field_type {
                    #default_val
                }
            });

            // Generate field initialization
            field_inits.push(quote! {
                #field_name: Self::#default_fn_name()
            });
        } else {
            // No default specified - use Default::default() or None for Option
            let is_option = is_option_type(field_type);

            if is_option {
                field_inits.push(quote! {
                    #field_name: None
                });
            } else {
                field_inits.push(quote! {
                    #field_name: Default::default()
                });
            }
        }
    }

    let expanded = quote! {
        impl #name {
            #(#default_fns)*
        }

        impl Default for #name {
            fn default() -> Self {
                Self {
                    #(#field_inits),*
                }
            }
        }
    };

    TokenStream::from(expanded)
}

/// Check if a type is Option<T>
fn is_option_type(ty: &syn::Type) -> bool {
    if let syn::Type::Path(type_path) = ty {
        if let Some(segment) = type_path.path.segments.last() {
            return segment.ident == "Option";
        }
    }
    false
}

/// Derive macro for automatic configuration loading
///
/// This macro generates methods to load configuration from files, environment variables,
/// and fallback chains with minimal boilerplate.
///
/// # Attributes
///
/// - `#[config(path = "path/to/config.yaml")]` - Default config file path
/// - `#[config(env_prefix = "MYAPP")]` - Environment variable prefix for loading from env
/// - `#[config(env_var = "MYAPP_CONFIG")]` - Environment variable for config file path override
///
/// # Example
///
/// ```ignore
/// use mecha10_macros::Config;
/// use serde::{Deserialize, Serialize};
///
/// #[derive(Debug, Clone, Serialize, Deserialize, Config)]
/// #[config(
///     path = "config/camera.yaml",
///     env_prefix = "CAMERA",
///     env_var = "CAMERA_CONFIG"
/// )]
/// pub struct CameraConfig {
///     pub fps: f32,
///     pub width: u32,
///     pub device: Option<String>,
/// }
///
/// // Generated methods:
/// // - CameraConfig::load() -> Result<Self>
/// // - CameraConfig::load_from_file(path: &str) -> Result<Self>
/// // - CameraConfig::load_from_env() -> Result<Self>
/// // - CameraConfig::load_with_fallback(default: Option<Self>) -> Result<Self>
/// ```
///
/// ## Usage:
///
/// ```ignore
/// // Load from default path or env var CAMERA_CONFIG
/// let config = CameraConfig::load()?;
///
/// // Load from specific file
/// let config = CameraConfig::load_from_file("custom.yaml")?;
///
/// // Load from environment variables (CAMERA_FPS, CAMERA_WIDTH, CAMERA_DEVICE)
/// let config = CameraConfig::load_from_env()?;
///
/// // Load with fallback chain:
/// // 1. Try CAMERA_CONFIG env var
/// // 2. Try config/camera.yaml
/// // 3. Try environment variables
/// // 4. Use provided default
/// let config = CameraConfig::load_with_fallback(Some(CameraConfig::default()))?;
/// ```
#[proc_macro_derive(Config, attributes(config))]
pub fn derive_config(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = &input.ident;

    // Parse struct-level config attributes
    let mut default_path = None;
    let mut env_prefix = None;
    let mut env_var = None;

    for attr in &input.attrs {
        if !attr.path().is_ident("config") {
            continue;
        }

        if let Ok(Meta::List(meta_list)) = attr.parse_args::<Meta>() {
            // Parse nested meta items
            let nested = meta_list.tokens.to_string();

            // Simple parsing for the nested attributes
            for item in nested.split(',') {
                let item = item.trim();
                if let Some(value) = item.strip_prefix("path = ") {
                    default_path = Some(value.trim_matches('"').to_string());
                } else if let Some(value) = item.strip_prefix("env_prefix = ") {
                    env_prefix = Some(value.trim_matches('"').to_string());
                } else if let Some(value) = item.strip_prefix("env_var = ") {
                    env_var = Some(value.trim_matches('"').to_string());
                }
            }
        }
    }

    let default_path = default_path.unwrap_or_else(|| format!("config/{}.yaml", name.to_string().to_lowercase()));
    let env_prefix = env_prefix.unwrap_or_else(|| name.to_string().to_uppercase());
    let env_var = env_var.unwrap_or_else(|| format!("{}_CONFIG", name.to_string().to_uppercase()));

    let expanded = quote! {
        impl #name {
            /// Load configuration using the default strategy:
            /// 1. Try environment variable for config file path
            /// 2. Try default config file path
            /// 3. Return error if neither exists
            pub fn load() -> ::mecha10_core::Result<Self> {
                use ::mecha10_core::config::load_config;
                use std::env;
                use std::path::Path;

                // Try environment variable for file path
                if let Ok(path) = env::var(#env_var) {
                    return load_config(&path);
                }

                // Try default file path
                if Path::new(#default_path).exists() {
                    return load_config(#default_path);
                }

                Err(::mecha10_core::Mecha10Error::Configuration(format!(
                    "No configuration found for {}. Tried: env var {}, file {}",
                    stringify!(#name),
                    #env_var,
                    #default_path
                )))
            }

            /// Load configuration from a specific file path
            ///
            /// Supports JSON, YAML, and TOML files based on extension.
            /// Environment variable substitution is supported with `${VAR_NAME}` syntax.
            pub fn load_from_file(path: &str) -> ::mecha10_core::Result<Self> {
                ::mecha10_core::config::load_config(path)
            }

            /// Load configuration from environment variables
            ///
            /// Uses the configured prefix to find environment variables.
            /// For example, with prefix "CAMERA", looks for CAMERA_FPS, CAMERA_WIDTH, etc.
            pub fn load_from_env() -> ::mecha10_core::Result<Self> {
                ::mecha10_core::config::load_config_from_env(#env_prefix)
            }

            /// Load configuration with full fallback chain:
            /// 1. Try environment variable for config file path
            /// 2. Try default config file path
            /// 3. Try environment variables with prefix
            /// 4. Use provided default configuration
            pub fn load_with_fallback(default_config: Option<Self>) -> ::mecha10_core::Result<Self> {
                ::mecha10_core::config::load_config_with_fallback(
                    #env_var,
                    #default_path,
                    #env_prefix,
                    default_config
                )
            }

            /// Get the default configuration file path
            pub fn default_config_path() -> &'static str {
                #default_path
            }

            /// Get the environment variable name for config file override
            pub fn config_env_var() -> &'static str {
                #env_var
            }

            /// Get the environment variable prefix for loading from env
            pub fn env_prefix() -> &'static str {
                #env_prefix
            }
        }
    };

    TokenStream::from(expanded)
}

/// Derive macro for automatic node boilerplate generation
///
/// This macro generates a `run()` function that handles:
/// - Configuration loading with fallback
/// - Context creation
/// - Instance support (optional)
/// - Health reporting configuration with priority levels
///
/// # Attributes
///
/// - `#[node(name = "node_name")]` - Node name (required)
/// - `#[node(instance_env = "NODE_INSTANCE")]` - Environment variable for instance name (optional, enables multi-instance support)
/// - `#[node(health_priority = "normal")]` - Health reporting priority (optional, defaults to "normal")
///   - `"critical"` - 1s interval, 5s TTL (motor-controller, estop, safety nodes)
///   - `"normal"` - 5s interval, 60s TTL (default, sensors, AI nodes)
///   - `"background"` - 30s interval, 120s TTL (logging, diagnostics)
///
/// # Example - Simple Node
///
/// ```ignore
/// use mecha10::prelude::*;
///
/// #[derive(Debug, Node)]
/// #[node(name = "camera")]
/// pub struct CameraNode {
///     config: CameraConfig,
/// }
///
/// #[derive(Debug, Clone, Serialize, Deserialize, Default)]
/// pub struct CameraConfig {
///     pub fps: f32,
/// }
///
/// #[async_trait]
/// impl NodeImpl for CameraNode {
///     type Config = CameraConfig;
///
///     async fn init(config: Self::Config) -> Result<Self> {
///         Ok(Self { config })
///     }
///
///     async fn run(&mut self, ctx: &Context) -> Result<()> {
///         // Node logic here
///         Ok(())
///     }
/// }
///
/// // The macro generates:
/// // pub async fn run() -> ::mecha10_core::Result<()> { ... }
/// ```
///
/// # Example - Critical Node (Safety-Critical)
///
/// ```ignore
/// use mecha10::prelude::*;
///
/// #[derive(Debug, Node)]
/// #[node(
///     name = "motor-controller",
///     health_priority = "critical"  // 1s heartbeat for safety nodes
/// )]
/// pub struct MotorControllerNode { ... }
/// ```
///
/// # Example - Background Node (Auxiliary)
///
/// ```ignore
/// use mecha10::prelude::*;
///
/// #[derive(Debug, Node)]
/// #[node(
///     name = "diagnostics",
///     health_priority = "background"  // 30s heartbeat for background nodes
/// )]
/// pub struct DiagnosticsNode { ... }
/// ```
///
/// # Example - Multi-Instance Node
///
/// ```ignore
/// use mecha10::prelude::*;
///
/// #[derive(Debug, Node)]
/// #[node(
///     name = "camera",
///     instance_env = "CAMERA_INSTANCE"  // Enables multi-instance support
/// )]
/// pub struct CameraNode { ... }
///
/// // Run with: CAMERA_INSTANCE=left ./camera
/// // Creates node "camera/left" with instance-scoped topics
/// ```
///
/// # Generated Code
///
/// The macro generates a `run()` function that:
/// 1. Loads config using the V2 config system with environment-aware fallback
/// 2. Creates a Context with the node name
/// 3. Optionally applies instance name from environment variable
/// 4. Calls `run_node()` with configured health reporting priority
///
/// This eliminates 15-20 lines of boilerplate per node.
#[proc_macro_derive(Node, attributes(node))]
pub fn derive_node(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);

    // Parse attributes using darling
    let opts = match NodeOpts::from_derive_input(&input) {
        Ok(opts) => opts,
        Err(e) => return e.write_errors().into(),
    };

    let node_type = &input.ident;
    let node_name = &opts.name;

    // Generate health config based on priority
    let health_config = match opts.health_priority.as_deref() {
        Some("critical") => quote! {
            ::mecha10_core::node::HealthReportingConfig::critical()
        },
        Some("background") => quote! {
            ::mecha10_core::node::HealthReportingConfig::background()
        },
        Some("normal") | None => quote! {
            ::mecha10_core::node::HealthReportingConfig::default()
        },
        Some(other) => {
            return syn::Error::new_spanned(
                &input,
                format!(
                    "Invalid health_priority '{}'. Valid values: 'critical', 'normal', 'background'",
                    other
                ),
            )
            .to_compile_error()
            .into();
        }
    };

    // Generate the run function based on whether instance support is enabled
    let run_fn = if let Some(instance_env) = opts.instance_env {
        // Multi-instance support
        quote! {
            /// Run this node with multi-instance support
            ///
            /// This function is auto-generated by the `#[derive(Node)]` macro.
            ///
            /// # Configuration
            ///
            /// Configuration is loaded with the following fallback chain:
            /// 1. File path from environment variable: `#config_env`
            /// 2. Default file path: `#config_path`
            /// 3. Environment variables with prefix: `#env_prefix`
            /// 4. Default configuration
            ///
            /// # Instance Support
            ///
            /// Instance name is read from environment variable: `#instance_env`
            ///
            /// If set, the node will run with instance-scoped topics and the instance
            /// name will be appended to the node ID (e.g., "camera_fake/left").
            ///
            /// # Examples
            ///
            /// ```bash
            /// # Run single instance
            /// ./camera_fake
            ///
            /// # Run with instance name
            /// CAMERA_INSTANCE=left ./camera_fake
            ///
            /// # Run with custom config
            /// CAMERA_CONFIG=custom.yaml CAMERA_INSTANCE=left ./camera_fake
            /// ```
            pub async fn run() -> ::mecha10_core::Result<()> {
                // Create context first (needed for config loading)
                let mut ctx = ::mecha10_core::context::Context::new(#node_name).await?;

                // Apply instance name if set
                if let Ok(instance) = std::env::var(#instance_env) {
                    ctx = ctx.with_instance(&instance);
                    ::mecha10_core::prelude::info!(
                        "Running node '{}' with instance '{}'",
                        #node_name,
                        instance
                    );
                } else {
                    ::mecha10_core::prelude::info!(
                        "Running node '{}' (no instance)",
                        #node_name
                    );
                }

                // Load config using V2 system with environment-aware file-level fallback
                let config: <#node_type as ::mecha10_core::node::NodeImpl>::Config =
                    ctx.load_node_config(#node_name).await?;

                // Run the node with configured health reporting
                ::mecha10_core::node::run_node::<#node_type>(
                    config,
                    ctx,
                    #health_config
                ).await
            }
        }
    } else {
        // Single-instance node
        quote! {
            /// Run this node
            ///
            /// This function is auto-generated by the `#[derive(Node)]` macro.
            ///
            /// # Configuration
            ///
            /// Configuration is loaded with the following fallback chain:
            /// 1. File path from environment variable: `#config_env`
            /// 2. Default file path: `#config_path`
            /// 3. Environment variables with prefix: `#env_prefix`
            /// 4. Default configuration
            ///
            /// # Examples
            ///
            /// ```bash
            /// # Run with default config
            /// ./my_node
            ///
            /// # Run with custom config file
            /// MY_NODE_CONFIG=custom.yaml ./my_node
            ///
            /// # Run with environment variable overrides
            /// MY_NODE_FPS=60 ./my_node
            /// ```
            pub async fn run() -> ::mecha10_core::Result<()> {
                // Create context first (needed for config loading)
                let ctx = ::mecha10_core::context::Context::new(#node_name).await?;

                // Load config using V2 system with environment-aware file-level fallback
                let config: <#node_type as ::mecha10_core::node::NodeImpl>::Config =
                    ctx.load_node_config(#node_name).await?;

                // Run the node with configured health reporting
                ::mecha10_core::node::run_node::<#node_type>(
                    config,
                    ctx,
                    #health_config
                ).await
            }
        }
    };

    TokenStream::from(run_fn)
}

/// Options for the Node derive macro
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(node), supports(struct_named))]
struct NodeOpts {
    /// Node name (required)
    name: String,

    /// Environment variable for instance name (optional, enables multi-instance support)
    instance_env: Option<String>,

    /// Health reporting priority level (optional, defaults to "normal")
    ///
    /// Valid values: "critical", "normal", "background"
    /// - "critical": 1s interval, 5s TTL (motor-controller, estop)
    /// - "normal": 5s interval, 60s TTL (default, sensors, AI nodes)
    /// - "background": 30s interval, 120s TTL (logging, diagnostics)
    #[darling(default)]
    health_priority: Option<String>,
    // Note: config_env, config_path, and env_prefix have been removed
    // with the V2 config system. Configuration is now loaded via
    // ctx.load_node_config() which handles environment-aware file-level fallback.
}
