//! Proc-macros for the Kailash plugin guest SDK.
//!
//! Provides `#[kailash_plugin]` for annotating plugin entry-point functions.
//! The macro generates the WASM ABI `kailash_execute` export that wraps the
//! user's function with JSON serialization and memory management.
//!
//! # Usage
//!
//! ```ignore
//! use kailash_plugin_guest::prelude::*;
//!
//! #[kailash_plugin(
//!     name = "my-plugin",
//!     description = "Processes data",
//!     version = "0.1.0",
//! )]
//! fn process(inputs: GuestValueMap) -> Result<GuestValueMap, PluginError> {
//!     Ok(inputs) // echo
//! }
//! ```

use proc_macro::TokenStream;
use quote::quote;
use syn::{
    parse::{Parse, ParseStream},
    parse_macro_input,
    punctuated::Punctuated,
    Ident, ItemFn, LitStr, Token,
};

// ---------------------------------------------------------------------------
// Attribute argument parsing
// ---------------------------------------------------------------------------

/// A single `key = "value"` pair in the macro attributes.
struct AttrKV {
    key: Ident,
    _eq: Token![=],
    value: AttrValue,
}

/// The right-hand side of an attribute `key = value`.
enum AttrValue {
    /// A string literal: `"my-plugin"`.
    Str(LitStr),
    /// An integer literal: `64`. Parsed but not yet used by any attribute.
    #[allow(dead_code)]
    Int(syn::LitInt),
}

impl Parse for AttrKV {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let key: Ident = input.parse()?;
        let _eq: Token![=] = input.parse()?;

        let value = if input.peek(LitStr) {
            AttrValue::Str(input.parse()?)
        } else {
            AttrValue::Int(input.parse()?)
        };

        Ok(AttrKV { key, _eq, value })
    }
}

/// Parsed collection of `#[kailash_plugin(...)]` attributes.
struct PluginAttrs {
    pairs: Punctuated<AttrKV, Token![,]>,
}

impl Parse for PluginAttrs {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let pairs = Punctuated::parse_terminated(input)?;
        Ok(PluginAttrs { pairs })
    }
}

/// Extracted and validated plugin metadata.
struct PluginMeta {
    name: String,
    description: String,
    version: String,
}

impl PluginMeta {
    fn from_attrs(attrs: &PluginAttrs) -> syn::Result<Self> {
        let mut name: Option<String> = None;
        let mut description: Option<String> = None;
        let mut version: Option<String> = None;

        for kv in &attrs.pairs {
            let key_str = kv.key.to_string();
            match key_str.as_str() {
                "name" => {
                    if let AttrValue::Str(lit) = &kv.value {
                        name = Some(lit.value());
                    } else {
                        return Err(syn::Error::new_spanned(
                            &kv.key,
                            "expected string literal for `name`",
                        ));
                    }
                },
                "description" => {
                    if let AttrValue::Str(lit) = &kv.value {
                        description = Some(lit.value());
                    } else {
                        return Err(syn::Error::new_spanned(
                            &kv.key,
                            "expected string literal for `description`",
                        ));
                    }
                },
                "version" => {
                    if let AttrValue::Str(lit) = &kv.value {
                        version = Some(lit.value());
                    } else {
                        return Err(syn::Error::new_spanned(
                            &kv.key,
                            "expected string literal for `version`",
                        ));
                    }
                },
                other => {
                    return Err(syn::Error::new_spanned(
                        &kv.key,
                        format!(
                            "unknown attribute `{other}`; expected `name`, `description`, or `version`"
                        ),
                    ));
                },
            }
        }

        let name = name.ok_or_else(|| {
            syn::Error::new(
                proc_macro2::Span::call_site(),
                "missing required attribute `name`",
            )
        })?;
        let description = description.unwrap_or_default();
        let version = version.unwrap_or_else(|| "0.1.0".to_owned());

        Ok(PluginMeta {
            name,
            description,
            version,
        })
    }
}

// ---------------------------------------------------------------------------
// Proc-macro entry point
// ---------------------------------------------------------------------------

/// Marks a function as a Kailash WASM plugin entry point.
///
/// The annotated function must have the signature:
///
/// ```ignore
/// fn name(inputs: GuestValueMap) -> Result<GuestValueMap, PluginError>
/// ```
///
/// # Attributes
///
/// | Attribute     | Type   | Required | Default   | Description                     |
/// |---------------|--------|----------|-----------|---------------------------------|
/// | `name`        | string | yes      | --        | Plugin name for the registry    |
/// | `description` | string | no       | `""`      | Human-readable description      |
/// | `version`     | string | no       | `"0.1.0"` | Semver version                  |
///
/// # Generated code
///
/// The macro generates:
///
/// 1. The original function, unchanged.
/// 2. A `_KAILASH_MANIFEST_*` constant containing the JSON manifest string.
/// 3. A `#[no_mangle] pub extern "C" fn kailash_execute(...)` that wraps
///    the user function through `kailash_plugin_guest::abi::execute_with_fn`.
///
/// # Example
///
/// ```ignore
/// use kailash_plugin_guest::prelude::*;
///
/// #[kailash_plugin(
///     name = "echo",
///     description = "Echoes inputs as outputs",
/// )]
/// fn echo(inputs: GuestValueMap) -> Result<GuestValueMap, PluginError> {
///     Ok(inputs)
/// }
/// ```
#[proc_macro_attribute]
pub fn kailash_plugin(attr: TokenStream, item: TokenStream) -> TokenStream {
    let attrs = parse_macro_input!(attr as PluginAttrs);
    let func = parse_macro_input!(item as ItemFn);

    let meta = match PluginMeta::from_attrs(&attrs) {
        Ok(m) => m,
        Err(e) => return e.to_compile_error().into(),
    };

    let func_name = &func.sig.ident;
    let plugin_name = &meta.name;
    let plugin_desc = &meta.description;
    let plugin_version = &meta.version;

    // Generate a unique manifest constant name to avoid collisions
    // if multiple plugins are defined in the same crate (unlikely but safe)
    let manifest_const_name = syn::Ident::new(
        &format!("_KAILASH_MANIFEST_{}", func_name.to_string().to_uppercase()),
        func_name.span(),
    );

    let expanded = quote! {
        // Keep the original function unchanged
        #func

        /// JSON-serialized plugin manifest generated by `#[kailash_plugin]`.
        #[doc(hidden)]
        #[allow(dead_code)]
        const #manifest_const_name: &str = concat!(
            r#"{"name":""#, #plugin_name,
            r#"","version":""#, #plugin_version,
            r#"","description":""#, #plugin_desc,
            r#"","abi_version":1,"inputs":[],"outputs":[]}"#
        );

        /// WASM ABI `kailash_execute` export generated by `#[kailash_plugin]`.
        ///
        /// Delegates to the user-defined function through the ABI trampoline.
        #[doc(hidden)]
        #[no_mangle]
        pub extern "C" fn kailash_execute(
            input_ptr: i32,
            input_len: i32,
            output_ptr_ptr: i32,
            output_len_ptr: i32,
        ) -> i32 {
            ::kailash_plugin_guest::abi::execute_with_fn(
                #func_name,
                input_ptr,
                input_len,
                output_ptr_ptr,
                output_len_ptr,
            )
        }
    };

    expanded.into()
}
