use proc_macro::TokenStream;
use quote::quote;
use std::sync::atomic::{AtomicBool, Ordering};

static SETUP_EMITTED: AtomicBool = AtomicBool::new(false);

fn ensure_setup() -> proc_macro2::TokenStream {
    if !SETUP_EMITTED.swap(true, Ordering::SeqCst) {
        quote! { ::uniffi::setup_scaffolding!(); }
    } else {
        quote! {}
    }
}

/// Export a function, method, impl block, or trait to foreign languages.
///
/// This wraps `#[uniffi::export]` and adds two convenience features:
///
/// 1. **Auto-setup** — the first `#[eqswift::export]` in your crate automatically
///    calls `uniffi::setup_scaffolding!()`. You no longer need `eqswift::setup!()`.
///
/// 2. **Auto-constructor** — inside an exported `impl` block, any method named
///    `new` that returns `Self` automatically gets `#[uniffi::constructor]`.
///
/// # Free functions
///
/// ```ignore
/// #[eqswift::export]
/// pub fn hello(name: String) -> String {
///     format!("Hello, {name}!")
/// }
/// ```
///
/// # Object methods
///
/// ```ignore
/// #[derive(eqswift::Object)]
/// pub struct Greeter;
///
/// #[eqswift::export]
/// impl Greeter {
///     // Auto-detected as constructor — no #[uniffi::constructor] needed
///     pub fn new() -> Self { Self }
///
///     pub fn greet(&self, name: String) -> String {
///         format!("Hello, {name}!")
///     }
/// }
/// ```
#[proc_macro_attribute]
pub fn export(args: TokenStream, input: TokenStream) -> TokenStream {
    let args = proc_macro2::TokenStream::from(args);
    let input = proc_macro2::TokenStream::from(input);

    let setup = ensure_setup();

    // Try to parse as an impl block to inject auto-constructor
    let parsed: Result<syn::ItemImpl, _> = syn::parse2(input.clone());
    let output = if let Ok(mut item_impl) = parsed {
        for item in &mut item_impl.items {
            if let syn::ImplItem::Fn(method) = item {
                if is_constructor_method(method) {
                    // Inject #[uniffi::constructor] if not already present
                    if !has_attr(&method.attrs, "constructor") {
                        method.attrs.push(syn::parse_quote! {
                            #[::uniffi::constructor]
                        });
                    }
                }
            }
        }
        quote! {
            #[::uniffi::export(#args)]
            #item_impl
        }
    } else {
        quote! {
            #[::uniffi::export(#args)]
            #input
        }
    };

    quote! {
        #setup
        #output
    }
    .into()
}

fn is_constructor_method(method: &syn::ImplItemFn) -> bool {
    if method.sig.ident != "new" {
        return false;
    }
    match &method.sig.output {
        syn::ReturnType::Default => false,
        syn::ReturnType::Type(_, ty) => is_self_type(ty),
    }
}

fn is_self_type(ty: &syn::Type) -> bool {
    match ty {
        syn::Type::Path(type_path) => {
            type_path.path.segments.len() == 1
                && type_path.path.segments.first().map(|s| s.ident == "Self").unwrap_or(false)
        }
        _ => false,
    }
}

fn has_attr(attrs: &[syn::Attribute], name: &str) -> bool {
    attrs.iter().any(|attr| {
        attr.path()
            .get_ident()
            .map(|i| i == name)
            .unwrap_or(false)
    })
}

/// One-time setup macro.
///
/// Expands to `uniffi::setup_scaffolding!()`. **You no longer need to call this**
/// manually — the first `#[eqswift::export]` will call it automatically. This
/// macro is kept for backward compatibility and for explicit control.
///
/// ```ignore
/// eqswift::setup!();
///
/// #[eqswift::export]
/// pub fn add(a: u32, b: u32) -> u32 { a + b }
/// ```
#[proc_macro]
pub fn setup(_input: TokenStream) -> TokenStream {
    // Only emit setup_scaffolding if export hasn't already done so.
    if !SETUP_EMITTED.swap(true, Ordering::SeqCst) {
        quote! {
            ::uniffi::setup_scaffolding!();
        }
        .into()
    } else {
        TokenStream::new()
    }
}
