use proc_macro::TokenStream;
use proc_macro_crate::{FoundCrate, crate_name};
use quote::quote;
use syn::{Data, DeriveInput, Fields, ItemFn, Lit, parse_macro_input};

/// Resolve the path to `epics_base_rs`, supporting both direct dependency
/// (`epics-base-rs`) and umbrella crate (`epics-rs`) usage.
fn epics_base_path() -> proc_macro2::TokenStream {
    if let Ok(found) = crate_name("epics-base-rs") {
        match found {
            FoundCrate::Itself => quote!(crate),
            FoundCrate::Name(name) => {
                let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
                quote!(::#ident)
            }
        }
    } else if let Ok(found) = crate_name("epics-rs") {
        match found {
            FoundCrate::Itself => quote!(crate::base),
            FoundCrate::Name(name) => {
                let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
                quote!(::#ident::base)
            }
        }
    } else {
        quote!(::epics_base_rs)
    }
}

/// Marks an `async fn main()` as an EPICS IOC entry point.
///
/// Builds a multi-threaded tokio runtime (via `epics_base_rs::__tokio`)
/// without requiring the downstream crate to depend on tokio directly.
///
/// # Restrictions
/// - Must be applied to `async fn main()` — no generics, no arguments.
/// - Does not accept attribute arguments (e.g., `#[epics_main(flavor = ...)]` is a compile error).
///
/// # Example
/// ```ignore
/// #[epics_main]
/// async fn main() -> CaResult<()> {
///     // ...
/// }
/// ```
#[proc_macro_attribute]
pub fn epics_main(attr: TokenStream, item: TokenStream) -> TokenStream {
    if !attr.is_empty() {
        return syn::Error::new(
            proc_macro2::Span::call_site(),
            "#[epics_main] does not accept arguments",
        )
        .to_compile_error()
        .into();
    }

    let input = parse_macro_input!(item as ItemFn);
    let sig = &input.sig;

    if sig.asyncness.is_none() {
        return syn::Error::new_spanned(sig.fn_token, "#[epics_main] requires `async fn`")
            .to_compile_error()
            .into();
    }
    if sig.ident != "main" {
        return syn::Error::new_spanned(&sig.ident, "#[epics_main] must be applied to `main`")
            .to_compile_error()
            .into();
    }
    if !sig.inputs.is_empty() {
        return syn::Error::new_spanned(&sig.inputs, "`main` must not take arguments")
            .to_compile_error()
            .into();
    }
    if !sig.generics.params.is_empty() || sig.generics.where_clause.is_some() {
        return syn::Error::new_spanned(&sig.generics, "`main` must not be generic")
            .to_compile_error()
            .into();
    }

    let attrs = &input.attrs;
    let vis = &input.vis;
    let ret = &sig.output;
    let body = &input.block;
    let base = epics_base_path();

    quote! {
        #(#attrs)*
        #vis fn main() #ret {
            #base::__tokio::runtime::Builder::new_multi_thread()
                .enable_all()
                .build()
                .expect("failed to build tokio runtime")
                .block_on(async move #body)
        }
    }
    .into()
}

/// Marks an async function as an EPICS test.
///
/// Builds a current-thread tokio runtime (via `epics_base_rs::__tokio`),
/// matching the default behavior of `#[tokio::test]`.
///
/// # Restrictions
/// - Must be applied to an `async fn` with no arguments and no generics.
/// - Does not accept attribute arguments.
///
/// # Example
/// ```ignore
/// #[epics_test]
/// async fn test_something() {
///     // ...
/// }
/// ```
#[proc_macro_attribute]
pub fn epics_test(attr: TokenStream, item: TokenStream) -> TokenStream {
    if !attr.is_empty() {
        return syn::Error::new(
            proc_macro2::Span::call_site(),
            "#[epics_test] does not accept arguments",
        )
        .to_compile_error()
        .into();
    }

    let input = parse_macro_input!(item as ItemFn);
    let sig = &input.sig;

    if sig.asyncness.is_none() {
        return syn::Error::new_spanned(sig.fn_token, "#[epics_test] requires `async fn`")
            .to_compile_error()
            .into();
    }
    if !sig.inputs.is_empty() {
        return syn::Error::new_spanned(&sig.inputs, "test functions must not take arguments")
            .to_compile_error()
            .into();
    }
    if !sig.generics.params.is_empty() || sig.generics.where_clause.is_some() {
        return syn::Error::new_spanned(&sig.generics, "test functions must not be generic")
            .to_compile_error()
            .into();
    }
    if input.attrs.iter().any(|a| a.path().is_ident("test")) {
        return syn::Error::new_spanned(
            input
                .attrs
                .iter()
                .find(|a| a.path().is_ident("test"))
                .unwrap(),
            "#[epics_test] already adds #[test]; remove the duplicate",
        )
        .to_compile_error()
        .into();
    }

    let attrs = &input.attrs;
    let vis = &input.vis;
    let name = &sig.ident;
    let ret = &sig.output;
    let body = &input.block;
    let base = epics_base_path();

    quote! {
        #[test]
        #(#attrs)*
        #vis fn #name() #ret {
            #base::__tokio::runtime::Builder::new_current_thread()
                .enable_all()
                .build()
                .expect("failed to build tokio runtime")
                .block_on(async move #body)
        }
    }
    .into()
}

/// Derive macro that implements the `Record` trait for a struct.
///
/// # Attributes
///
/// - `#[record(type = "ai")]` — sets the record type name
/// - `#[record(type = "ai", crate_path = "my_crate")]` — override crate path
/// - `#[field(type = "Double")]` — sets the DBR type for a field
/// - `#[field(type = "Double", read_only)]` — marks a field as read-only
///
/// Field names are converted from snake_case to UPPER_CASE for EPICS field names.
#[proc_macro_derive(EpicsRecord, attributes(record, field))]
pub fn derive_epics_record(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    match impl_epics_record(&input) {
        Ok(ts) => ts.into(),
        Err(e) => e.to_compile_error().into(),
    }
}

struct RecordAttrs {
    record_type: String,
    crate_path: Option<String>,
}

struct FieldInfo {
    ident: syn::Ident,
    epics_name: String,
    dbf_type: String,
    read_only: bool,
}

fn impl_epics_record(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
    let name = &input.ident;
    let attrs = parse_record_attrs(input)?;
    let record_type_str = &attrs.record_type;

    // Determine crate path
    let krate: proc_macro2::TokenStream = match &attrs.crate_path {
        Some(p) => p.parse().unwrap(),
        None => quote! { crate },
    };

    // Parse fields
    let fields = match &input.data {
        Data::Struct(data) => match &data.fields {
            Fields::Named(fields) => fields,
            _ => {
                return Err(syn::Error::new_spanned(
                    input,
                    "EpicsRecord requires named fields",
                ));
            }
        },
        _ => {
            return Err(syn::Error::new_spanned(
                input,
                "EpicsRecord can only be derived for structs",
            ));
        }
    };

    let mut field_infos = Vec::new();
    for f in &fields.named {
        let ident = f.ident.as_ref().unwrap().clone();
        let (dbf_type, read_only) = parse_field_attrs(f)?;
        let epics_name = ident.to_string().to_uppercase();
        field_infos.push(FieldInfo {
            ident,
            epics_name,
            dbf_type,
            read_only,
        });
    }

    let field_count = field_infos.len();

    // Generate field_list entries
    let field_descs: Vec<_> = field_infos
        .iter()
        .map(|fi| {
            let name_str = &fi.epics_name;
            let dbf = dbf_type_ident(&fi.dbf_type);
            let ro = fi.read_only;
            quote! {
                #krate::server::record::FieldDesc {
                    name: #name_str,
                    dbf_type: #krate::types::DbFieldType::#dbf,
                    read_only: #ro,
                }
            }
        })
        .collect();

    // Generate get_field match arms
    let get_arms: Vec<_> = field_infos
        .iter()
        .map(|fi| {
            let epics_name = &fi.epics_name;
            let ident = &fi.ident;
            let conversion = value_to_epics(&krate, &fi.dbf_type, quote!(self.#ident));
            quote! {
                #epics_name => Some(#conversion),
            }
        })
        .collect();

    // Generate put_field match arms
    let put_arms: Vec<_> = field_infos
        .iter()
        .map(|fi| {
            let epics_name = &fi.epics_name;
            let ident = &fi.ident;
            if fi.read_only {
                quote! {
                    #epics_name => {
                        return Err(#krate::error::CaError::ReadOnlyField(
                            #epics_name.to_string()
                        ));
                    }
                }
            } else {
                let extraction = value_from_epics(&krate, &fi.dbf_type, ident);
                quote! {
                    #epics_name => { #extraction }
                }
            }
        })
        .collect();

    let expanded = quote! {
        impl #krate::server::record::Record for #name {
            fn record_type(&self) -> &'static str {
                #record_type_str
            }

            fn field_list(&self) -> &'static [#krate::server::record::FieldDesc] {
                static FIELDS: [#krate::server::record::FieldDesc; #field_count] = [
                    #(#field_descs),*
                ];
                &FIELDS
            }

            fn get_field(&self, name: &str) -> Option<#krate::types::EpicsValue> {
                match name {
                    #(#get_arms)*
                    _ => None,
                }
            }

            fn put_field(&mut self, name: &str, value: #krate::types::EpicsValue) -> #krate::error::CaResult<()> {
                self.validate_put(name, &value)?;
                match name {
                    #(#put_arms)*
                    _ => {
                        return Err(#krate::error::CaError::FieldNotFound(name.to_string()));
                    }
                }
                self.on_put(name);
                Ok(())
            }
        }
    };

    Ok(expanded)
}

fn parse_record_attrs(input: &DeriveInput) -> syn::Result<RecordAttrs> {
    let mut record_type = None;
    let mut crate_path = None;

    for attr in &input.attrs {
        if attr.path().is_ident("record") {
            attr.parse_nested_meta(|meta| {
                if meta.path.is_ident("type") {
                    let value = meta.value()?;
                    let lit: Lit = value.parse()?;
                    if let Lit::Str(s) = lit {
                        record_type = Some(s.value());
                    }
                    Ok(())
                } else if meta.path.is_ident("crate_path") {
                    let value = meta.value()?;
                    let lit: Lit = value.parse()?;
                    if let Lit::Str(s) = lit {
                        crate_path = Some(s.value());
                    }
                    Ok(())
                } else {
                    Err(meta.error("expected `type` or `crate_path`"))
                }
            })?;
        }
    }

    let record_type = record_type
        .ok_or_else(|| syn::Error::new_spanned(input, "missing #[record(type = \"...\")]"))?;

    Ok(RecordAttrs {
        record_type,
        crate_path,
    })
}

fn parse_field_attrs(field: &syn::Field) -> syn::Result<(String, bool)> {
    let mut dbf_type = None;
    let mut read_only = false;

    for attr in &field.attrs {
        if attr.path().is_ident("field") {
            attr.parse_nested_meta(|meta| {
                if meta.path.is_ident("type") {
                    let value = meta.value()?;
                    let lit: Lit = value.parse()?;
                    if let Lit::Str(s) = lit {
                        dbf_type = Some(s.value());
                    }
                    Ok(())
                } else if meta.path.is_ident("read_only") {
                    read_only = true;
                    Ok(())
                } else {
                    Err(meta.error("expected `type` or `read_only`"))
                }
            })?;
        }
    }

    let dbf_type = dbf_type
        .ok_or_else(|| syn::Error::new_spanned(field, "missing #[field(type = \"...\")]"))?;

    Ok((dbf_type, read_only))
}

fn dbf_type_ident(type_str: &str) -> proc_macro2::Ident {
    proc_macro2::Ident::new(type_str, proc_macro2::Span::call_site())
}

fn value_to_epics(
    krate: &proc_macro2::TokenStream,
    dbf_type: &str,
    field_expr: proc_macro2::TokenStream,
) -> proc_macro2::TokenStream {
    match dbf_type {
        "Double" => quote! { #krate::types::EpicsValue::Double(#field_expr) },
        "Float" => quote! { #krate::types::EpicsValue::Float(#field_expr) },
        "Short" => quote! { #krate::types::EpicsValue::Short(#field_expr) },
        "Long" => quote! { #krate::types::EpicsValue::Long(#field_expr) },
        "Int64" => quote! { #krate::types::EpicsValue::Int64(#field_expr) },
        "Char" => quote! { #krate::types::EpicsValue::Char(#field_expr) },
        "Enum" => quote! { #krate::types::EpicsValue::Enum(#field_expr) },
        "String" => quote! { #krate::types::EpicsValue::String(#field_expr.clone()) },
        _ => quote! { compile_error!("unknown field type") },
    }
}

fn value_from_epics(
    krate: &proc_macro2::TokenStream,
    dbf_type: &str,
    field_ident: &syn::Ident,
) -> proc_macro2::TokenStream {
    // Enum fields accept Enum, Long, and Short values (common in asyn drivers)
    if dbf_type == "Enum" {
        return quote! {
            match value {
                #krate::types::EpicsValue::Enum(v) => { self.#field_ident = v; }
                #krate::types::EpicsValue::Long(v) => { self.#field_ident = v as u16; }
                #krate::types::EpicsValue::Short(v) => { self.#field_ident = v as u16; }
                _ => {
                    return Err(#krate::error::CaError::TypeMismatch(
                        stringify!(#field_ident).to_uppercase().to_string()
                    ));
                }
            }
        };
    }

    let variant = match dbf_type {
        "Double" => "Double",
        "Float" => "Float",
        "Short" => "Short",
        "Long" => "Long",
        "Int64" => "Int64",
        "Char" => "Char",
        "String" => "String",
        _ => return quote! { compile_error!("unknown field type"); },
    };

    let variant_ident = proc_macro2::Ident::new(variant, proc_macro2::Span::call_site());

    quote! {
        if let #krate::types::EpicsValue::#variant_ident(v) = value {
            self.#field_ident = v;
        } else {
            return Err(#krate::error::CaError::TypeMismatch(
                stringify!(#field_ident).to_uppercase().to_string()
            ));
        }
    }
}

// ── PVA Typed NT + service framework ─────────────────────────────────

/// Resolve the path to `epics_pva_rs` crate. Mirrors
/// [`epics_base_path`] for the PVA macros below.
fn epics_pva_path() -> proc_macro2::TokenStream {
    if let Ok(found) = crate_name("epics-pva-rs") {
        match found {
            FoundCrate::Itself => quote!(crate),
            FoundCrate::Name(name) => {
                let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
                quote!(::#ident)
            }
        }
    } else if let Ok(found) = crate_name("epics-rs") {
        match found {
            FoundCrate::Itself => quote!(crate::pva),
            FoundCrate::Name(name) => {
                let ident = proc_macro2::Ident::new(&name, proc_macro2::Span::call_site());
                quote!(::#ident::pva)
            }
        }
    } else {
        quote!(::epics_pva_rs)
    }
}

/// `#[derive(NTScalar)]` — generate a [`TypedNT`] impl for the
/// annotated struct. The struct must have:
///
/// - exactly one field named `value` of a primitive type (`f64`,
///   `i32`, `String`, etc.) — encoded as the NTScalar `value` slot
/// - any number of additional fields. Fields tagged
///   `#[nt(meta)]` get encoded as their type's
///   [`TypedNT`] impl (`Alarm`, `TimeStamp`, custom meta structs).
///
/// Generated code emits `epics:nt/NTScalar:1.0` as the wrapper
/// struct id; meta fields are inlined alongside `value`.
///
/// ```ignore
/// use epics_pva_rs::nt::{Alarm, TimeStamp};
///
/// #[derive(epics_macros_rs::NTScalar)]
/// struct MotorPos {
///     value: f64,
///     #[nt(meta)] alarm: Alarm,
///     #[nt(meta)] timestamp: TimeStamp,
/// }
/// ```
#[proc_macro_derive(NTScalar, attributes(nt))]
pub fn derive_nt_scalar(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let krate = epics_pva_path();
    let name = &input.ident;

    // The derive does not yet handle generic / lifetime params —
    // `descriptor()` would need to resolve every `T: TypedNT`
    // bound at expansion time, and the runtime helpers don't know
    // how to wrap a borrow. Reject up front with a clear message
    // instead of emitting code that fails with a confusing trait
    // resolution error 200 lines later.
    if !input.generics.params.is_empty() {
        return syn::Error::new_spanned(
            &input.generics,
            "NTScalar derive does not support generic or lifetime \
             parameters; implement TypedNT manually for parameterised types",
        )
        .to_compile_error()
        .into();
    }

    let fields = match &input.data {
        Data::Struct(s) => match &s.fields {
            Fields::Named(named) => &named.named,
            _ => {
                return syn::Error::new_spanned(
                    name,
                    "NTScalar derive requires a struct with named fields",
                )
                .to_compile_error()
                .into();
            }
        },
        _ => {
            return syn::Error::new_spanned(name, "NTScalar derive only works on structs")
                .to_compile_error()
                .into();
        }
    };

    let mut value_field: Option<(syn::Ident, syn::Type)> = None;
    let mut meta_fields: Vec<(syn::Ident, syn::Type)> = Vec::new();
    for field in fields {
        let Some(ident) = field.ident.clone() else {
            continue;
        };
        let is_meta = field.attrs.iter().any(|a| {
            a.path().is_ident("nt")
                && a.parse_nested_meta(|m| {
                    if m.path.is_ident("meta") {
                        Ok(())
                    } else {
                        Err(m.error("unknown #[nt(...)] arg"))
                    }
                })
                .is_ok()
        });
        if is_meta {
            meta_fields.push((ident, field.ty.clone()));
        } else if ident == "value" {
            value_field = Some((ident, field.ty.clone()));
        } else {
            // Other fields are forbidden so the generated descriptor
            // stays predictable. Operators that need richer NT shapes
            // can still implement TypedNT manually.
            return syn::Error::new_spanned(
                ident,
                "NTScalar derive: only `value` and `#[nt(meta)]`-tagged fields allowed",
            )
            .to_compile_error()
            .into();
        }
    }

    let Some((value_ident, value_ty)) = value_field else {
        return syn::Error::new_spanned(name, "NTScalar derive requires a `value` field")
            .to_compile_error()
            .into();
    };

    let meta_field_names: Vec<String> = meta_fields.iter().map(|(i, _)| i.to_string()).collect();
    let meta_field_idents: Vec<&syn::Ident> = meta_fields.iter().map(|(i, _)| i).collect();
    let meta_field_tys: Vec<&syn::Type> = meta_fields.iter().map(|(_, t)| t).collect();

    let value_ty_path = quote!(<#value_ty as #krate::nt::TypedNT>::descriptor());
    let value_to_field =
        quote!(<#value_ty as #krate::nt::TypedNT>::to_pv_field(&self.#value_ident));
    let value_from_field = quote! {
        <#value_ty as #krate::nt::TypedNT>::from_pv_field(__field)
            .map_err(|e| __rt::wrong_type("value", &e.to_string()))?
    };

    // Extract the inner `value` PvField from the parent wrapper
    // structure and pass it to the value type's TypedNT impl. The
    // primitive impls (and Vec<T>, EnumValue, ...) all expect to
    // see their full-wrapper shape (e.g. `epics:nt/NTScalar:1.0` for
    // f64, `epics:nt/NTScalarArray:1.0` for Vec<f64>), so we
    // re-wrap the raw `value` field in the inner type's expected
    // wrapper before forwarding.
    let value_extract = quote! {
        {
            let raw = __s
                .get_field("value")
                .ok_or_else(|| __rt::missing("value"))?;
            let __wrap_sid = match #value_ty_path {
                __rt::FieldDesc::Structure { struct_id, .. } => struct_id,
                _ => "epics:nt/NTScalar:1.0".to_string(),
            };
            let mut __wrap = __rt::PvStructure::new(&__wrap_sid);
            __wrap
                .fields
                .push(("value".into(), raw.clone()));
            let __field = __rt::PvField::Structure(__wrap);
            let __field = &__field;
            #value_from_field
        }
    };

    let expanded = quote! {
        impl #krate::nt::TypedNT for #name {
            fn descriptor() -> #krate::pvdata::FieldDesc {
                use #krate::nt::typed::__rt;
                let mut __fields: ::std::vec::Vec<(::std::string::String, __rt::FieldDesc)> =
                    ::std::vec::Vec::new();
                // The `value` slot's wire type is taken from the
                // user's `value` field type's TypedNT::descriptor —
                // that descriptor is itself an NTScalar wrapper, so
                // we pull out its `value` field. Falling back to
                // Variant when the wrapper looks unexpected keeps
                // user-defined nested NT compositions working.
                // I-2: derive the wrapper struct_id from the inner
                // value type's descriptor so the same derive macro
                // covers NTScalar / NTScalarArray / NTEnum.
                let __inner = #value_ty_path;
                let (__sid, __value_field) = match __inner {
                    __rt::FieldDesc::Structure { struct_id, fields } => {
                        let __value_field = fields.into_iter()
                            .find(|(n, _)| n == "value")
                            .map(|(_, f)| f)
                            .unwrap_or(__rt::FieldDesc::Variant);
                        (struct_id, __value_field)
                    }
                    other => ("epics:nt/NTScalar:1.0".to_string(), other),
                };
                __fields.push(("value".into(), __value_field));
                #(
                    __fields.push((
                        #meta_field_names.into(),
                        <#meta_field_tys as #krate::nt::TypedNT>::descriptor(),
                    ));
                )*
                __rt::FieldDesc::Structure {
                    struct_id: __sid,
                    fields: __fields,
                }
            }

            fn to_pv_field(&self) -> #krate::pvdata::PvField {
                use #krate::nt::typed::__rt;
                let __sid = match #value_ty_path {
                    __rt::FieldDesc::Structure { struct_id, .. } => struct_id,
                    _ => "epics:nt/NTScalar:1.0".to_string(),
                };
                let mut __s = __rt::PvStructure::new(&__sid);
                // Inner TypedNT impl may return either a bare scalar
                // or an NTScalar wrapper struct. Unwrap to grab the
                // `value` slot so the parent struct stays
                // single-level.
                let __inner_field = #value_to_field;
                let __value_slot = match __inner_field {
                    __rt::PvField::Structure(inner) => {
                        inner.fields
                            .into_iter()
                            .find(|(n, _)| n == "value")
                            .map(|(_, f)| f)
                            .unwrap_or(__rt::PvField::Scalar(__rt::ScalarValue::Int(0)))
                    }
                    other => other,
                };
                __s.fields.push(("value".into(), __value_slot));
                #(
                    __s.fields.push((
                        #meta_field_names.into(),
                        <#meta_field_tys as #krate::nt::TypedNT>::to_pv_field(&self.#meta_field_idents),
                    ));
                )*
                __rt::PvField::Structure(__s)
            }

            fn from_pv_field(
                __field: &#krate::pvdata::PvField,
            ) -> ::std::result::Result<Self, #krate::nt::TypedNTError> {
                use #krate::nt::typed::__rt;
                let __s = match __field {
                    __rt::PvField::Structure(s) => s,
                    _ => return Err(__rt::wrong_type("<root>", "expected NTScalar wrapper")),
                };
                // I-2: accept any wrapper id, including
                // NTScalar / NTScalarArray / NTEnum / empty,
                // since the derive emits whatever the value's
                // TypedNT impl declared. Concrete shape mismatch
                // surfaces inside `from_pv_field` for the value
                // type via WrongType.
                let __expected_sid = match #value_ty_path {
                    __rt::FieldDesc::Structure { struct_id, .. } => struct_id,
                    _ => "epics:nt/NTScalar:1.0".to_string(),
                };
                if !(__s.struct_id.is_empty() || __s.struct_id == __expected_sid) {
                    return Err(__rt::wrong_struct_id(&__expected_sid, &__s.struct_id));
                }
                let __value: #value_ty = #value_extract;
                #(
                    let #meta_field_idents: #meta_field_tys = {
                        let raw = __s
                            .get_field(#meta_field_names)
                            .ok_or_else(|| __rt::missing(#meta_field_names))?;
                        <#meta_field_tys as #krate::nt::TypedNT>::from_pv_field(raw)?
                    };
                )*
                Ok(Self {
                    #value_ident: __value,
                    #( #meta_field_idents, )*
                })
            }
        }
    };

    expanded.into()
}

/// `#[pva_service]` — turn an `impl Block` for a service struct
/// into a [`PvaService`] (in `epics_pva_rs::service`). Every async
/// method becomes a wire-callable RPC; positional parameters are
/// extracted from the request struct's named fields, the return
/// value is encoded via [`IntoServiceResponse`].
///
/// Restrictions:
/// - methods must be `&self` async
/// - parameters must implement `ServiceArg`
/// - return type must implement `IntoServiceResponse`
///
/// ```ignore
/// struct MotorService { driver: Arc<Driver> }
///
/// #[epics_macros_rs::pva_service]
/// impl MotorService {
///     async fn r#move(&self, target: f64, velocity: f64) -> Result<f64, String> {
///         self.driver.start(target, velocity).await
///     }
/// }
/// ```
#[proc_macro_attribute]
pub fn pva_service(_attr: TokenStream, item: TokenStream) -> TokenStream {
    let krate = epics_pva_path();
    let input = parse_macro_input!(item as syn::ItemImpl);
    let self_ty = &input.self_ty;

    let mut method_arms = Vec::new();
    for item in &input.items {
        let syn::ImplItem::Fn(m) = item else {
            continue;
        };
        // Only async fn(&self, ...) methods are exposed.
        if m.sig.asyncness.is_none() {
            continue;
        }
        let mut iter = m.sig.inputs.iter();
        match iter.next() {
            Some(syn::FnArg::Receiver(_)) => {}
            _ => continue, // skip non-method functions
        }
        let method_name = m.sig.ident.to_string();
        let method_ident = &m.sig.ident;

        let mut arg_names: Vec<String> = Vec::new();
        let mut arg_idents: Vec<syn::Ident> = Vec::new();
        let mut arg_tys: Vec<syn::Type> = Vec::new();
        for arg in iter {
            let syn::FnArg::Typed(pat_ty) = arg else {
                continue;
            };
            let syn::Pat::Ident(pat_ident) = &*pat_ty.pat else {
                continue;
            };
            arg_names.push(pat_ident.ident.to_string());
            arg_idents.push(pat_ident.ident.clone());
            arg_tys.push((*pat_ty.ty).clone());
        }

        let dispatch_arm = quote! {
            {
                let __svc: ::std::sync::Arc<#self_ty> = self.clone();
                #krate::service::ServiceMethod {
                    name: #method_name.into(),
                    dispatch: ::std::sync::Arc::new(move |__req: #krate::pvdata::PvField| {
                        let __svc = __svc.clone();
                        ::std::boxed::Box::pin(async move {
                            let __args = #krate::service::Args::from_pv_field(&__req);
                            #(
                                let #arg_idents: #arg_tys = __args
                                    .get_named::<#arg_tys>(#arg_names)?;
                            )*
                            let __out = __svc.#method_ident(#( #arg_idents ),*).await;
                            Ok(#krate::service::types::IntoServiceResponse::into_service_response(__out))
                        })
                    }),
                }
            }
        };
        method_arms.push(dispatch_arm);
    }

    let impl_block = &input;
    let expanded = quote! {
        #impl_block

        impl #krate::service::PvaService for #self_ty {
            fn methods(self: ::std::sync::Arc<Self>) -> ::std::vec::Vec<#krate::service::ServiceMethod> {
                ::std::vec![ #( #method_arms ),* ]
            }
        }
    };
    expanded.into()
}

/// `#[derive(NTTable)]` — generate a [`TypedNT`] impl for a
/// table-shaped struct. Every field of the annotated struct must
/// be a `Vec<T>` where `T: TypedNT` (the column type). Field
/// names become column labels.
///
/// Wire shape:
/// ```text
/// epics:nt/NTTable:1.0 {
///     labels: Vec<String>,    // column names (declared field order)
///     value: {                // one Vec<T> per column
///         col1: Vec<T1>,
///         col2: Vec<T2>,
///         ...
///     },
/// }
/// ```
///
/// ```ignore
/// use epics_pva_rs::nt::derive::NTTable;
///
/// #[derive(NTTable)]
/// struct ScanResult {
///     timestamp: Vec<f64>,
///     position:  Vec<f64>,
///     intensity: Vec<f64>,
/// }
/// ```
#[proc_macro_derive(NTTable)]
pub fn derive_nt_table(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let krate = epics_pva_path();
    let name = &input.ident;

    if !input.generics.params.is_empty() {
        return syn::Error::new_spanned(
            &input.generics,
            "NTTable derive does not support generic or lifetime parameters; \
             implement TypedNT manually for parameterised tables",
        )
        .to_compile_error()
        .into();
    }

    let fields = match &input.data {
        Data::Struct(s) => match &s.fields {
            Fields::Named(named) => &named.named,
            _ => {
                return syn::Error::new_spanned(
                    name,
                    "NTTable derive requires a struct with named fields",
                )
                .to_compile_error()
                .into();
            }
        },
        _ => {
            return syn::Error::new_spanned(name, "NTTable derive only works on structs")
                .to_compile_error()
                .into();
        }
    };

    let mut col_idents: Vec<syn::Ident> = Vec::new();
    let mut col_tys: Vec<syn::Type> = Vec::new();
    for field in fields {
        let Some(ident) = field.ident.clone() else {
            continue;
        };
        col_idents.push(ident);
        col_tys.push(field.ty.clone());
    }
    let col_names: Vec<String> = col_idents.iter().map(|i| i.to_string()).collect();

    if col_idents.is_empty() {
        return syn::Error::new_spanned(name, "NTTable derive requires at least one column field")
            .to_compile_error()
            .into();
    }

    // Each column is encoded as Vec<T>; we extract the wire-format
    // descriptor's `value` slot from <Vec<T> as TypedNT>::descriptor()
    // and wrap into the inner `value` struct of NTTable.
    let expanded = quote! {
        impl #krate::nt::TypedNT for #name {
            fn descriptor() -> #krate::pvdata::FieldDesc {
                use #krate::nt::typed::__rt;
                let mut __value_fields: ::std::vec::Vec<(::std::string::String, __rt::FieldDesc)> =
                    ::std::vec::Vec::new();
                #(
                    let __inner = <#col_tys as #krate::nt::TypedNT>::descriptor();
                    let __slot = match __inner {
                        __rt::FieldDesc::Structure { fields, .. } => {
                            fields.into_iter()
                                .find(|(n, _)| n == "value")
                                .map(|(_, f)| f)
                                .unwrap_or(__rt::FieldDesc::Variant)
                        }
                        other => other,
                    };
                    __value_fields.push((#col_names.into(), __slot));
                )*
                __rt::FieldDesc::Structure {
                    struct_id: "epics:nt/NTTable:1.0".into(),
                    fields: ::std::vec![
                        ("labels".into(), __rt::FieldDesc::ScalarArray(__rt::ScalarType::String)),
                        ("value".into(), __rt::FieldDesc::Structure {
                            struct_id: "".into(),
                            fields: __value_fields,
                        }),
                    ],
                }
            }

            fn to_pv_field(&self) -> #krate::pvdata::PvField {
                use #krate::nt::typed::__rt;
                let mut __value_struct = __rt::PvStructure::new("");
                #(
                    let __col_field = <#col_tys as #krate::nt::TypedNT>::to_pv_field(&self.#col_idents);
                    let __slot = match __col_field {
                        __rt::PvField::Structure(inner) => {
                            inner.fields.into_iter()
                                .find(|(n, _)| n == "value")
                                .map(|(_, f)| f)
                                .unwrap_or(__rt::PvField::Scalar(__rt::ScalarValue::Int(0)))
                        }
                        other => other,
                    };
                    __value_struct.fields.push((#col_names.into(), __slot));
                )*
                let mut __labels: ::std::vec::Vec<__rt::ScalarValue> = ::std::vec::Vec::new();
                #(
                    __labels.push(__rt::ScalarValue::String(#col_names.into()));
                )*
                let mut __root = __rt::PvStructure::new("epics:nt/NTTable:1.0");
                __root.fields.push((
                    "labels".into(),
                    __rt::PvField::ScalarArray(__labels),
                ));
                __root.fields.push((
                    "value".into(),
                    __rt::PvField::Structure(__value_struct),
                ));
                __rt::PvField::Structure(__root)
            }

            fn from_pv_field(
                __field: &#krate::pvdata::PvField,
            ) -> ::std::result::Result<Self, #krate::nt::TypedNTError> {
                use #krate::nt::typed::__rt;
                let __s = match __field {
                    __rt::PvField::Structure(s) => s,
                    _ => return Err(__rt::wrong_type("<root>", "expected NTTable wrapper")),
                };
                if !(__s.struct_id.is_empty() || __s.struct_id == "epics:nt/NTTable:1.0") {
                    return Err(__rt::wrong_struct_id("epics:nt/NTTable:1.0", &__s.struct_id));
                }
                let __value_struct = match __s.get_field("value") {
                    ::std::option::Option::Some(__rt::PvField::Structure(v)) => v,
                    _ => return Err(__rt::missing("value")),
                };
                #(
                    let #col_idents: #col_tys = {
                        let raw = __value_struct
                            .get_field(#col_names)
                            .ok_or_else(|| __rt::missing(#col_names))?;
                        // Wrap the raw column value in the inner type's
                        // expected NTScalarArray wrapper.
                        let __wrap_sid = match <#col_tys as #krate::nt::TypedNT>::descriptor() {
                            __rt::FieldDesc::Structure { struct_id, .. } => struct_id,
                            _ => "epics:nt/NTScalarArray:1.0".to_string(),
                        };
                        let mut __wrap = __rt::PvStructure::new(&__wrap_sid);
                        __wrap.fields.push(("value".into(), raw.clone()));
                        let __wrapped = __rt::PvField::Structure(__wrap);
                        <#col_tys as #krate::nt::TypedNT>::from_pv_field(&__wrapped)
                            .map_err(|e| __rt::wrong_type(#col_names, &e.to_string()))?
                    };
                )*
                Ok(Self {
                    #( #col_idents, )*
                })
            }
        }
    };
    expanded.into()
}
