//! Attribute-argument parsing and small `syn` helpers shared by the macros.
//!
//! All parsing returns `syn::Result` with spanned errors so diagnostics point
//! at the offending token rather than the whole attribute.

use proc_macro2::Span;
use syn::{
    GenericArgument, Generics, Ident, LitStr, PathArguments, ReturnType, Token, TraitBound, Type,
    TypeParamBound, WherePredicate,
    parse::{Parse, ParseStream},
    punctuated::Punctuated,
    token,
};

/// Arguments of `#[service(...)]` / `#[component(...)]`. All optional:
/// - `id` / `name` — default to the type name (`name` is also the RPC prefix);
/// - `version` — service version;
/// - `provide = dyn Trait` or `provide = [dyn A, dyn B]` — traits this component
///   provides, injectable as `Arc<dyn Trait>` / `Vec<_>` / `HashMap<String, _>`.
///   The trait must be `Send + Sync` (state it as a supertrait: `trait Trait:
///   Send + Sync`), so no use site needs to write `+ Send + Sync`;
/// - `qualifier = ".."` — key for `HashMap<String, Arc<dyn Trait>>` injection;
/// - `primary` — mark this the primary provider for every trait it provides;
/// - `by_value` — store/inject this component as `Self` rather than `Arc<Self>`
///   (for cheap-to-clone, typically internally-`Arc`, types);
/// - `scope = singleton | connection | request | transient` — the lifetime of the
///   instance (default `singleton`). Only valid on `#[component]`; a `#[service]`
///   is always a singleton.
#[derive(Default)]
pub struct ServiceArgs {
    pub id: Option<LitStr>,
    pub name: Option<LitStr>,
    pub version: Option<LitStr>,
    pub provide: Vec<Type>,
    pub qualifier: Option<LitStr>,
    pub primary: bool,
    pub by_value: bool,
    /// The `ComponentScope` variant ident (`Singleton`/`Connection`/`Request`/
    /// `Transient`) parsed from `scope = ..`, ready to splice after
    /// `ComponentScope::`. `None` means the default (singleton).
    pub scope: Option<Ident>,
    /// Overrides the generated per-service RPC slice name (`rpc_slice = Ident`).
    /// `None` defaults to `{Service}Rpcs`. An escape hatch when that name collides
    /// with something already in scope; a `#[handlers]` block for the service must
    /// then pass the same `rpc_slice = ..`.
    pub rpc_slice: Option<Ident>,
    /// Overrides the generated per-type factory slice name (`factory_slice = Ident`).
    /// `None` defaults to `{Type}Factories`. An escape hatch for a name collision; a
    /// `#[methods]` block contributing an `#[init]` to this type must then pass the
    /// same `factory_slice = ..`.
    pub factory_slice: Option<Ident>,
    /// An explicit async factory path (`factory = path::to::fn`). When set, that
    /// function (a [`Factory`](overseerd_core::Factory)) is registered as the
    /// component's constructor instead of (or alongside) field injection.
    pub factory: Option<syn::Path>,
    /// Suppresses the field-injection default factory (`default_factory = false`).
    /// With no other factory this makes the component **manual** — provided via
    /// `DaemonBuilder::with_component` rather than constructed.
    pub no_default_factory: bool,
}

impl Parse for ServiceArgs {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut args = ServiceArgs::default();

        while !input.is_empty() {
            let key: Ident = input.parse()?;

            match key.to_string().as_str() {
                "id" => args.id = Some(parse_value(input)?),
                "name" => args.name = Some(parse_value(input)?),
                "version" => args.version = Some(parse_value(input)?),
                "qualifier" => args.qualifier = Some(parse_value(input)?),
                "primary" => args.primary = true,
                "by_value" => args.by_value = true,
                "rpc_slice" => {
                    input.parse::<Token![=]>()?;
                    args.rpc_slice = Some(input.parse()?);
                }
                "factory_slice" => {
                    input.parse::<Token![=]>()?;
                    args.factory_slice = Some(input.parse()?);
                }
                "factory" => {
                    input.parse::<Token![=]>()?;
                    args.factory = Some(input.parse()?);
                }
                "default_factory" => {
                    input.parse::<Token![=]>()?;
                    let value: syn::LitBool = input.parse()?;
                    args.no_default_factory = !value.value;
                }
                "scope" => {
                    input.parse::<Token![=]>()?;
                    let value: Ident = input.parse()?;

                    let variant = match value.to_string().as_str() {
                        "singleton" => "Singleton",
                        "connection" => "Connection",
                        "request" => "Request",
                        "transient" => "Transient",
                        other => {
                            return Err(syn::Error::new(
                                value.span(),
                                format!(
                                    "unknown scope `{other}`, expected `singleton`, \
                                     `connection`, `request`, or `transient`"
                                ),
                            ));
                        }
                    };

                    args.scope = Some(Ident::new(variant, value.span()));
                }
                "provide" => {
                    input.parse::<Token![=]>()?;

                    if input.peek(token::Bracket) {
                        let content;
                        syn::bracketed!(content in input);
                        let list =
                            Punctuated::<ProvidedTrait, Token![,]>::parse_terminated(&content)?;
                        args.provide.extend(list.into_iter().map(|t| t.0));
                    } else {
                        args.provide.push(input.parse::<ProvidedTrait>()?.0);
                    }
                }
                other => {
                    return Err(syn::Error::new(
                        key.span(),
                        format!(
                            "unknown argument `{other}`, expected `id`, `name`, `version`, \
                             `provide`, `qualifier`, `primary`, `by_value`, `scope`, \
                             `rpc_slice`, `factory_slice`, `factory`, or `default_factory`"
                        ),
                    ));
                }
            }

            if input.peek(Token![,]) {
                input.parse::<Token![,]>()?;
            }
        }

        Ok(args)
    }
}

/// Parses the `= "value"` half of a string-valued argument.
fn parse_value(input: ParseStream) -> syn::Result<LitStr> {
    input.parse::<Token![=]>()?;

    input.parse()
}

/// A provided-trait entry, parsed as a trait-object [`Type`] (`dyn Trait`) so the
/// IDE treats it as a type — completions, go-to-definition, and highlighting —
/// rather than an opaque path. The trait must be `Send + Sync` via supertraits.
struct ProvidedTrait(Type);

impl Parse for ProvidedTrait {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let ty: Type = input.parse()?;

        if !matches!(ty, Type::TraitObject(_)) {
            return Err(syn::Error::new_spanned(
                &ty,
                "`provide` expects a trait object, e.g. `dyn Repo`",
            ));
        }

        Ok(ProvidedTrait(ty))
    }
}

/// Arguments of `#[methods(factory_slice = Ident)]`. The only key is the optional
/// `factory_slice`, which must match the owning `#[component]`/`#[service]`'s
/// `factory_slice` when it was overridden; `None` defaults to `{Type}Factories`.
#[derive(Default)]
pub struct MethodsArgs {
    pub factory_slice: Option<Ident>,
}

impl Parse for MethodsArgs {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut args = MethodsArgs::default();

        while !input.is_empty() {
            let key: Ident = input.parse()?;

            match key.to_string().as_str() {
                "factory_slice" => {
                    input.parse::<Token![=]>()?;
                    args.factory_slice = Some(input.parse()?);
                }
                other => {
                    return Err(syn::Error::new(
                        key.span(),
                        format!("unknown `methods` argument `{other}`, expected `factory_slice`"),
                    ));
                }
            }

            if input.peek(Token![,]) {
                input.parse::<Token![,]>()?;
            }
        }

        Ok(args)
    }
}

/// Arguments of `#[handlers(client_trait = Name)]`. The only key is the optional
/// `client_trait`: when present the generated client is emitted as a trait `Name`
/// plus its impl (mockable, `dyn`-compatible); when absent, as a plain inherent impl.
pub struct HandlersArgs {
    pub client_trait: Option<Ident>,
    /// Overrides the per-service RPC slice this block appends to (`rpc_slice = Ident`).
    /// Must match the `rpc_slice` passed to the owning `#[service]`. `None` defaults
    /// to `{Service}Rpcs`.
    pub rpc_slice: Option<Ident>,
}

impl Parse for HandlersArgs {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut client_trait = None;
        let mut rpc_slice = None;

        let args = Punctuated::<HandlersArg, Token![,]>::parse_terminated(input)?;

        for arg in args {
            match arg {
                HandlersArg::ClientTrait(ident) => client_trait = Some(ident),
                HandlersArg::RpcSlice(ident) => rpc_slice = Some(ident),
            }
        }

        Ok(HandlersArgs {
            client_trait,
            rpc_slice,
        })
    }
}

/// A single recognized `#[handlers(...)]` argument.
enum HandlersArg {
    ClientTrait(Ident),
    RpcSlice(Ident),
}

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

        match key.to_string().as_str() {
            "client_trait" => {
                let value: Ident = input.parse()?;

                Ok(HandlersArg::ClientTrait(value))
            }
            "rpc_slice" => {
                let value: Ident = input.parse()?;

                Ok(HandlersArg::RpcSlice(value))
            }
            other => Err(syn::Error::new(
                key.span(),
                format!(
                    "unknown handlers argument `{other}`, expected `client_trait` or `rpc_slice`"
                ),
            )),
        }
    }
}

/// Maps the `(streamed_input, streamed_output)` pair inferred from a handler
/// signature to its `OperationKind` variant ident. The kind is structural, so
/// `#[rpc]` derives it rather than taking an annotation.
pub fn operation_ident(streamed_input: bool, streamed_output: bool) -> Ident {
    let name = match (streamed_input, streamed_output) {
        (false, false) => "Unary",
        (false, true) => "ServerStream",
        (true, false) => "ClientStream",
        (true, true) => "BidiStream",
    };

    Ident::new(name, Span::call_site())
}

/// Whether a return type's body is a `ResponseStream<T>` (after peeling an
/// optional outer `Result`), i.e. the handler streams its output.
pub fn returns_response_stream(output: &ReturnType) -> bool {
    let ReturnType::Type(_, ty) = output else {
        return false;
    };

    type_name(peel_named(ty, "Result")).is_some_and(|id| id == "ResponseStream")
}

/// Whether a parameter type is the inbound-stream extractor `Streaming<T>`.
pub fn is_streaming_param(ty: &Type) -> bool {
    type_name(ty).is_some_and(|id| id == "Streaming")
}

/// Whether a parameter type is the single-body extractor `Payload<T>`.
pub fn is_payload_param(ty: &Type) -> bool {
    type_name(ty).is_some_and(|id| id == "Payload")
}

/// The logical response *body* type, for descriptor metadata only.
///
/// Handlers may return any [`Responder`](overseerd_core::Responder): a bare value,
/// `Result<T, E>`, `ResponseStream<T>`, `Result<ResponseStream<T>, E>`, `()`,
/// etc. This peels the `Result` and `ResponseStream` wrappers to the body type,
/// and reports `()` for an absent return. It never fails: dispatch is uniform
/// regardless of the shape, so this only feeds the `output` field.
pub fn response_body_type(output: &ReturnType) -> Type {
    let ty = match output {
        ReturnType::Default => return syn::parse_quote!(()),
        ReturnType::Type(_, ty) => ty.as_ref(),
    };

    peel_named(peel_named(ty, "Result"), "ResponseStream").clone()
}

/// The last path-segment ident of a simple path type (e.g. `Foo` of `a::b::Foo<T>`).
fn type_name(ty: &Type) -> Option<&Ident> {
    match ty {
        Type::Path(path) => path.path.segments.last().map(|segment| &segment.ident),
        _ => None,
    }
}

/// The first type argument of `Name<T, ..>` when the last path segment is `name`.
/// Used by the client codegen to recover request/response payload types from the
/// extractor and `Responder` wrappers in a handler signature.
fn first_type_arg(ty: &Type, name: &str) -> Option<Type> {
    if let Type::Path(path) = ty
        && let Some(segment) = path.path.segments.last()
        && segment.ident == name
        && let PathArguments::AngleBracketed(generics) = &segment.arguments
        && let Some(GenericArgument::Type(inner)) = generics.args.first()
    {
        return Some(inner.clone());
    }

    None
}

/// The handle type `H` of an `Option<H>` field (an optional dependency).
pub fn option_inner(ty: &Type) -> Option<Type> {
    first_type_arg(ty, "Option")
}

/// The item handle `H` of a `Vec<H>` field (a collection of all trait providers).
pub fn vec_inner(ty: &Type) -> Option<Type> {
    first_type_arg(ty, "Vec")
}

/// The value handle `H` of a `HashMap<String, H>` field (qualifier-keyed trait
/// providers). Only matches when the key is `String`.
pub fn hashmap_value(ty: &Type) -> Option<Type> {
    let Type::Path(path) = ty else {
        return None;
    };
    let segment = path.path.segments.last()?;

    if segment.ident != "HashMap" {
        return None;
    }

    let PathArguments::AngleBracketed(generics) = &segment.arguments else {
        return None;
    };

    let mut types = generics.args.iter().filter_map(|arg| match arg {
        GenericArgument::Type(t) => Some(t),
        _ => None,
    });
    let key = types.next()?;
    let value = types.next()?;

    if type_name(key).is_some_and(|id| id == "String") {
        return Some(value.clone());
    }

    None
}

/// The handle type `H` of a `Dynamic<H>` field (a runtime-provided dependency).
pub fn dynamic_inner(ty: &Type) -> Option<Type> {
    first_type_arg(ty, "Dynamic")
}

/// The config type `T` of a `Cfg<T>` field (a property-path-bound config value).
pub fn cfg_inner(ty: &Type) -> Option<Type> {
    first_type_arg(ty, "Cfg")
}

/// The request body type `T` of a `Payload<T>` parameter.
pub fn payload_inner(ty: &Type) -> Option<Type> {
    first_type_arg(ty, "Payload")
}

/// The request item type `T` of a `Streaming<T>` parameter.
pub fn streaming_inner(ty: &Type) -> Option<Type> {
    first_type_arg(ty, "Streaming")
}

/// If `output` is `Result<Ok, Err?>`, returns `(Ok, Err)`. `Err` is `None` for a
/// one-argument alias such as `overseerd::Result<T>` (whose error is the framework
/// `Error`, opaque to the client and surfaced as a raw body).
pub fn result_type_args(output: &ReturnType) -> Option<(Type, Option<Type>)> {
    let ReturnType::Type(_, ty) = output else {
        return None;
    };

    let Type::Path(path) = ty.as_ref() else {
        return None;
    };

    let segment = path.path.segments.last()?;

    if segment.ident != "Result" {
        return None;
    }

    let PathArguments::AngleBracketed(generics) = &segment.arguments else {
        return None;
    };

    let mut args = generics.args.iter().filter_map(|arg| match arg {
        GenericArgument::Type(t) => Some(t.clone()),
        _ => None,
    });

    let ok = args.next()?;
    let err = args.next();

    Some((ok, err))
}

/// If `ty` is `Name<T, ..>`, returns its first type argument `T`; otherwise `ty`.
fn peel_named<'a>(ty: &'a Type, name: &str) -> &'a Type {
    if let Type::Path(path) = ty
        && let Some(segment) = path.path.segments.last()
        && segment.ident == name
        && let PathArguments::AngleBracketed(generics) = &segment.arguments
        && let Some(GenericArgument::Type(inner)) = generics.args.first()
    {
        return inner;
    }

    ty
}

/// Extracts `T` from `Arc<T>` (the form `#[init]` dependency parameters take).
pub fn arc_inner_type(ty: &Type) -> syn::Result<Type> {
    let err = || {
        syn::Error::new_spanned(
            ty,
            "#[init] parameters must be `Arc<Component>` dependencies",
        )
    };

    let Type::Path(path) = ty else {
        return Err(err());
    };

    let segment = path.path.segments.last().ok_or_else(err)?;

    if segment.ident != "Arc" {
        return Err(err());
    }

    let PathArguments::AngleBracketed(generics) = &segment.arguments else {
        return Err(err());
    };

    match generics.args.first() {
        Some(GenericArgument::Type(inner)) => Ok(inner.clone()),
        _ => Err(err()),
    }
}

/// The item shape of a detected `Stream`, recovered from an `impl Stream<Item =
/// ..>` type or a generic parameter bound by `Stream`.
///
/// `error` is `Some` when the item is `Result<T, E>` (a per-item-fallible
/// stream): `item` is then the success type `T` and `error` the `E`. For a bare
/// `Item = T` stream `error` is `None`.
pub struct StreamShape {
    pub item: Type,
    pub error: Option<Type>,
}

impl StreamShape {
    /// Whether each item is a `Result` the framework maps to a `StreamError`.
    pub fn fallible(&self) -> bool {
        self.error.is_some()
    }
}

/// Detects whether `ty` is a `Stream` the macro can introspect — either
/// `impl Stream<Item = ..>` or a generic parameter of `generics` bound by
/// `Stream` — and recovers its [`StreamShape`]. A concrete named type is opaque
/// here (the macro cannot prove it is a `Stream`), so it returns `None`; such
/// returns are flagged explicitly with `#[rpc(stream)]`.
pub fn stream_shape(ty: &Type, generics: &Generics) -> Option<StreamShape> {
    let item = match ty {
        Type::ImplTrait(it) => stream_item_in_bounds(&it.bounds)?,

        Type::Path(path) if path.qself.is_none() => {
            let name = path.path.get_ident()?;

            generic_stream_item(name, generics)?
        }

        _ => return None,
    };

    Some(shape_from_item(item))
}

/// Splits a stream's `Item` type into its success/error parts: `Result<T, E>`
/// becomes a fallible shape, anything else a bare-value shape.
fn shape_from_item(item: Type) -> StreamShape {
    match result_args_of_type(&item) {
        Some((ok, err)) => StreamShape {
            item: ok,
            error: Some(err),
        },

        None => StreamShape { item, error: None },
    }
}

/// Finds the `Item = X` binding of a `Stream` bound among `bounds`, if present.
fn stream_item_in_bounds(bounds: &Punctuated<TypeParamBound, Token![+]>) -> Option<Type> {
    bounds.iter().find_map(|bound| match bound {
        TypeParamBound::Trait(trait_bound) => stream_item_of_bound(trait_bound),
        _ => None,
    })
}

/// The `Item = X` of a single `Stream<Item = X>` trait bound, or `None` if the
/// bound is not `Stream` or names no `Item`.
fn stream_item_of_bound(bound: &TraitBound) -> Option<Type> {
    let segment = bound.path.segments.last()?;

    if segment.ident != "Stream" {
        return None;
    }

    let PathArguments::AngleBracketed(generics) = &segment.arguments else {
        return None;
    };

    generics.args.iter().find_map(|arg| match arg {
        GenericArgument::AssocType(assoc) if assoc.ident == "Item" => Some(assoc.ty.clone()),
        _ => None,
    })
}

/// The `Item` of a generic parameter `name` bound by `Stream`, checking both the
/// inline bounds (`<S: Stream<..>>`) and the `where` clause.
fn generic_stream_item(name: &Ident, generics: &Generics) -> Option<Type> {
    let inline = generics.params.iter().find_map(|param| match param {
        syn::GenericParam::Type(type_param) if &type_param.ident == name => {
            stream_item_in_bounds(&type_param.bounds)
        }
        _ => None,
    });

    if inline.is_some() {
        return inline;
    }

    let where_clause = generics.where_clause.as_ref()?;

    where_clause
        .predicates
        .iter()
        .find_map(|predicate| match predicate {
            WherePredicate::Type(predicate) if type_name(&predicate.bounded_ty) == Some(name) => {
                stream_item_in_bounds(&predicate.bounds)
            }
            _ => None,
        })
}

/// If `ty` is `Result<Ok, Err>`, returns `(Ok, Err)`. Used to split a stream
/// item into success and error halves.
fn result_args_of_type(ty: &Type) -> Option<(Type, Type)> {
    let Type::Path(path) = ty else {
        return None;
    };

    let segment = path.path.segments.last()?;

    if segment.ident != "Result" {
        return None;
    }

    let PathArguments::AngleBracketed(generics) = &segment.arguments else {
        return None;
    };

    let mut args = generics.args.iter().filter_map(|arg| match arg {
        GenericArgument::Type(t) => Some(t.clone()),
        _ => None,
    });

    let ok = args.next()?;
    let err = args.next()?;

    Some((ok, err))
}

/// Whether a return type is a `Result<...>` (vs. an infallible bare value).
pub fn returns_result(output: &ReturnType) -> bool {
    let ReturnType::Type(_, ty) = output else {
        return false;
    };

    let Type::Path(path) = ty.as_ref() else {
        return false;
    };

    path.path
        .segments
        .last()
        .is_some_and(|segment| segment.ident == "Result")
}
