//! Shared field-injection factory generation, used by `#[service]` (as the
//! overridable default) and `#[component]` (as the component's factory).
//!
//! Emits a singleton factory that constructs the struct field by field. Every
//! field is a **dependency** resolved from the container, unless it carries
//! `#[default]`, which makes it local owned state built via `Default::default()`.
//! A dependency field's type is its [`Injectable`] *handle*; the wrapper around
//! that handle selects the edge shape:
//! - `H` — a required single dependency (`Arc<T>`, or a by-value `Injectable`);
//! - `Option<H>` — an optional dependency (resolves to `None` if absent);
//! - `Dynamic<H>` — a runtime-provided dependency (exempt from static validation);
//! - `Option<Dynamic<H>>` — both.
//!
//! Each dependency is keyed under `<H as Injectable>::Target`, so a blanket
//! `Arc<T>` keys by `T` while a by-value handle keys by itself.
//!
//! [`Injectable`]: overseerd_core::Injectable

use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use syn::{Expr, ExprLit, Field, Fields, ItemStruct, Lit, LitStr, Meta, spanned::Spanned};

use crate::{attr, paths::overseerd_path};

/// The per-type factory slice identifier, `{Type}Factories`. The `#[component]` /
/// `#[service]` macro declares it; each `#[init]` / `factory = ..` appends to it.
pub fn factories_slice_ident(self_ident: &syn::Ident) -> syn::Ident {
    format_ident!("{}Factories", self_ident)
}

/// Declares the module-level `{Type}Factories` distributed slice and the
/// `ComponentFactories` impl returning it. Module-level (and `pub`) so a `#[methods]`
/// / `#[handlers]` block in another module can append to it via `use`. Mirrors the
/// `{Service}Rpcs` slice + `ServiceRpcs` impl.
pub fn factories_infrastructure(self_ident: &syn::Ident, slice: &syn::Ident) -> TokenStream {
    let component_factories = overseerd_path("ComponentFactories");
    let component_factory_descriptor = overseerd_path("ComponentFactoryDescriptor");
    let distributed_slice = overseerd_path("linkme::distributed_slice");
    let linkme_crate = overseerd_path("linkme");

    quote! {
        #[#distributed_slice]
        #[linkme(crate = #linkme_crate)]
        #[allow(non_upper_case_globals)]
        pub static #slice: [#component_factory_descriptor];

        impl #component_factories for #self_ident {
            fn factories() -> &'static [#component_factory_descriptor] {
                &#slice
            }
        }
    }
}

pub fn field_injection_component(
    item: &mut ItemStruct,
    id: &LitStr,
    name: &LitStr,
    defer_di_assert: bool,
    scope_variant: &syn::Ident,
    factories_slice: &syn::Ident,
    emit_default_factory: bool,
) -> TokenStream {
    let self_ident = item.ident.clone();
    let boxed_component = overseerd_path("BoxedComponent");
    let component_construction_context = overseerd_path("ComponentConstructionContext");
    let component_descriptor = overseerd_path("ComponentDescriptor");
    let component_factories = overseerd_path("ComponentFactories");
    let component_factory_descriptor = overseerd_path("ComponentFactoryDescriptor");
    let component_scope = overseerd_path("ComponentScope");
    let components_slice = overseerd_path("COMPONENTS");
    let dependency_descriptor = overseerd_path("DependencyDescriptor");
    let component = overseerd_path("Component");
    let distributed_slice = overseerd_path("linkme::distributed_slice");
    let linkme_crate = overseerd_path("linkme");
    let result = overseerd_path("Result");
    let type_descriptor = overseerd_path("TypeDescriptor");
    let descriptor_trait = overseerd_path("Descriptor");

    let mut inits = Vec::new();
    let mut dep_descriptors = Vec::new();
    let mut checks = Vec::new();
    let mut wired_targets = Vec::new();

    let mut plan = |inits: &mut Vec<TokenStream>, prefix: TokenStream, field: &mut Field| {
        let FieldPlan {
            value,
            dependency,
            check,
            wired,
        } = plan_field(field);

        inits.push(quote!(#prefix #value));

        if let Some(dep) = dependency {
            dep_descriptors.push(dep);
        }

        if let Some(check) = check {
            checks.push(check);
        }

        if let Some(wired) = wired {
            wired_targets.push(wired);
        }
    };

    let construct = match &mut item.fields {
        Fields::Named(named) => {
            for field in &mut named.named {
                let field_ident = field.ident.clone().expect("named field");
                plan(&mut inits, quote!(#field_ident:), field);
            }

            quote!(#self_ident { #(#inits),* })
        }

        Fields::Unnamed(unnamed) => {
            for field in &mut unnamed.unnamed {
                plan(&mut inits, quote!(), field);
            }

            quote!(#self_ident( #(#inits),* ))
        }

        Fields::Unit => quote!(#self_ident),
    };

    // The field-injection default factory — its dep assertions, construction fn,
    // and slice entry. Suppressed for a manual component (`default_factory = false`),
    // which is provided as an instance rather than built.
    let default_factory = if emit_default_factory {
        // Assert deps eagerly only when the field-injection factory is the real one.
        // A `#[service]` (and any type whose construction an `#[init]` may override)
        // defers to the `#[init]` path and the source analyzer, so its field deps are
        // not necessarily the real ones.
        let di_assert = if defer_di_assert {
            quote!()
        } else {
            crate::di::assert(&checks)
        };

        // The lazy `Wired` predicate — every single dep, incl. trait objects —
        // checked when `app!` demands `T: Wired`.
        let wired = crate::di::wired_impl(&self_ident, &wired_targets);

        quote! {
            #di_assert

            #wired

            #[allow(unused_variables)]
            fn __overseerd_factory(
                cx: &mut #component_construction_context,
            ) -> ::core::pin::Pin<
                ::std::boxed::Box<
                    dyn ::core::future::Future<
                        Output = #result<#boxed_component>,
                    > + ::core::marker::Send + '_,
                >,
            > {
                ::std::boxed::Box::pin(async move {
                    let __instance = #construct;

                    ::core::result::Result::Ok(#boxed_component {
                        ty: #type_descriptor::of::<#self_ident>(#name),
                        value: ::std::boxed::Box::new(
                            <#self_ident as #component>::into_handle(__instance),
                        ),
                    })
                })
            }

            fn __overseerd_deps() -> ::std::vec::Vec<#dependency_descriptor> {
                ::std::vec![ #(#dep_descriptors),* ]
            }

            // The field-injection default, appended to the type's factory slice. Used
            // only when no explicit (`#[init]` / `factory = ..`) factory is present.
            #[#distributed_slice(#factories_slice)]
            #[linkme(crate = #linkme_crate)]
            static __OVERSEERD_DEFAULT_FACTORY: #component_factory_descriptor =
                #component_factory_descriptor {
                    construct: __overseerd_factory,
                    dependencies: __overseerd_deps,
                    default: true,
                };
        }
    } else {
        // A manual component still strips field attrs (done above) but builds
        // nothing. It is trivially `Wired` — it has no constructed dependencies.
        let _ = (&construct, &dep_descriptors, &checks, &wired_targets);

        crate::di::wired_impl(&self_ident, &[])
    };

    quote! {
        #default_factory

        const __OVERSEERD_COMPONENT_DESCRIPTOR: #component_descriptor =
            #component_descriptor {
                id: #id,
                name: #name,
                ty: #type_descriptor::of::<#self_ident>(#name),
                scope: #component_scope::#scope_variant,
                factories: <#self_ident as #component_factories>::factories,
            };

        impl #descriptor_trait<#component_descriptor> for #self_ident {
            const DESCRIPTOR: #component_descriptor = __OVERSEERD_COMPONENT_DESCRIPTOR;
        }

        #[#distributed_slice(#components_slice)]
        #[linkme(crate = #linkme_crate)]
        static __OVERSEERD_COMPONENT: #component_descriptor = __OVERSEERD_COMPONENT_DESCRIPTOR;
    }
}

/// Emits an explicit `factory = path` factory entry: it appends a non-default
/// [`ComponentFactoryDescriptor`] to the type's factory slice, driving the given
/// async factory through the build-time `Factory` machinery (so the path's
/// signature need not be visible — its deps come from its parameters' `FromContainer`
/// impls). Overrides the field-injection default.
pub fn explicit_factory(factory_path: &syn::Path, factories_slice: &syn::Ident) -> TokenStream {
    let component_construction_context = overseerd_path("ComponentConstructionContext");
    let component_factory_descriptor = overseerd_path("ComponentFactoryDescriptor");
    let dependency_descriptor = overseerd_path("DependencyDescriptor");
    let dispatch_factory = overseerd_path("dispatch_factory");
    let factory_dependencies = overseerd_path("factory_dependencies");
    let boxed_component = overseerd_path("BoxedComponent");
    let distributed_slice = overseerd_path("linkme::distributed_slice");
    let linkme_crate = overseerd_path("linkme");
    let result = overseerd_path("Result");

    quote! {
        fn __overseerd_explicit_deps() -> ::std::vec::Vec<#dependency_descriptor> {
            #factory_dependencies(#factory_path)
        }

        #[allow(unused_variables)]
        fn __overseerd_explicit_factory(
            cx: &mut #component_construction_context,
        ) -> ::core::pin::Pin<
            ::std::boxed::Box<
                dyn ::core::future::Future<
                    Output = #result<#boxed_component>,
                > + ::core::marker::Send + '_,
            >,
        > {
            #dispatch_factory(#factory_path, cx)
        }

        #[#distributed_slice(#factories_slice)]
        #[linkme(crate = #linkme_crate)]
        static __OVERSEERD_EXPLICIT_FACTORY: #component_factory_descriptor =
            #component_factory_descriptor {
                construct: __overseerd_explicit_factory,
                dependencies: __overseerd_explicit_deps,
                default: false,
            };
    }
}

/// One field's contribution: the value expression that builds it in the struct
/// literal, and the dependency descriptor it registers (absent for `#[default]`
/// local state).
struct FieldPlan {
    value: TokenStream,
    dependency: Option<TokenStream>,
    /// The `Provide<Target>` type to assert eagerly for this field, when it is a
    /// required *concrete* dependency (trait-object / optional / dynamic / multi
    /// edges are not eagerly asserted in the per-macro path).
    check: Option<TokenStream>,
    /// The `Provide<Target>` type for the lazy `Wired` bound — every required
    /// single dependency, *including* trait objects (checked only via `app!`).
    wired: Option<TokenStream>,
}

/// Classifies a field and, as a side effect, strips the `#[default]` marker so
/// the emitted struct stays valid.
fn plan_field(field: &mut Field) -> FieldPlan {
    let had_default = field.attrs.iter().any(|a| a.path().is_ident("default"));
    field.attrs.retain(|a| !a.path().is_ident("default"));

    if had_default {
        return FieldPlan {
            value: quote!(::core::default::Default::default()),
            dependency: None,
            check: None,
            wired: None,
        };
    }

    // `#[qualifier = ".."]` selects a specific provider for a single trait edge.
    let field_qualifier = field.attrs.iter().find_map(|a| {
        if a.path().is_ident("qualifier")
            && let Meta::NameValue(nv) = &a.meta
            && let Expr::Lit(ExprLit {
                lit: Lit::Str(s), ..
            }) = &nv.value
        {
            return Some(s.clone());
        }

        None
    });
    field.attrs.retain(|a| !a.path().is_ident("qualifier"));

    // `#[config]` (sole-binding shorthand) or `#[config("app.db.reader")]` (path)
    // marks a config-binding injection.
    let config_attr = field.attrs.iter().find(|a| a.path().is_ident("config"));
    let is_config = config_attr.is_some();
    let config_path: Option<LitStr> = config_attr.and_then(|a| match &a.meta {
        Meta::List(list) => syn::parse2::<LitStr>(list.tokens.clone()).ok(),
        _ => None,
    });
    field.attrs.retain(|a| !a.path().is_ident("config"));

    let cardinality = overseerd_path("Cardinality");
    let dynamic_ty = overseerd_path("Dynamic");
    let error = overseerd_path("Error");
    let injectable = overseerd_path("Injectable");
    let type_descriptor = overseerd_path("TypeDescriptor");
    let dependency_descriptor = overseerd_path("DependencyDescriptor");

    let dep = |handle: &syn::Type,
               kind: TokenStream,
               optional: bool,
               dynamic: bool,
               qualifier: TokenStream| {
        let dep_name_str = match attr::arc_inner_type(handle) {
            Ok(inner) => inner.to_token_stream().to_string(),
            Err(_) => handle.to_token_stream().to_string(),
        };
        let dep_name = LitStr::new(&dep_name_str, handle.span());

        quote! {
            #dependency_descriptor {
                name: #dep_name,
                ty: #type_descriptor::of::<<#handle as #injectable>::Target>(#dep_name),
                cardinality: #kind,
                optional: #optional,
                dynamic: #dynamic,
                qualifier: #qualifier,
                config: false,
            }
        }
    };

    let none = quote!(::core::option::Option::None);

    let ty = &field.ty;

    // A `#[config]` / `#[config("path")]` field is a config binding injected as
    // `Cfg<T>`, keyed by property path. Config edges are validated against the
    // registered bindings (not the component graph), so they emit no di-check
    // assertion.
    if is_config {
        let handle = ty.clone();
        let dep_name_str = match attr::cfg_inner(&handle) {
            Some(inner) => inner.to_token_stream().to_string(),
            None => handle.to_token_stream().to_string(),
        };
        let dep_name = LitStr::new(&dep_name_str, handle.span());

        let (resolved, qualifier) = match &config_path {
            Some(path) => (
                quote!(cx.resolve_config::<#handle>(#path).await),
                quote!(::core::option::Option::Some(#path)),
            ),
            None => (
                quote!(cx.resolve_config_sole::<#handle>().await),
                none.clone(),
            ),
        };

        let dependency = quote! {
            #dependency_descriptor {
                name: #dep_name,
                ty: #type_descriptor::of::<<#handle as #injectable>::Target>(#dep_name),
                cardinality: #cardinality::One,
                optional: false,
                dynamic: false,
                qualifier: #qualifier,
                config: true,
            }
        };

        return FieldPlan {
            value: quote!(#resolved.ok_or(#error::MissingComponent(#dep_name))?),
            dependency: Some(dependency),
            check: None,
            wired: None,
        };
    }

    // Multi-valued edges resolve every provider of a trait and never fail (empty
    // `Vec`/`HashMap` is valid), so they take no `Injectable`-missing fallback.
    if let Some(item) = attr::vec_inner(ty) {
        let dependency = dep(
            &item,
            quote!(#cardinality::Collection),
            false,
            false,
            none.clone(),
        );

        return FieldPlan {
            value: quote!(cx.resolve_all::<#item>().await),
            dependency: Some(dependency),
            check: None,
            wired: None,
        };
    }

    if let Some(value) = attr::hashmap_value(ty) {
        let dependency = dep(
            &value,
            quote!(#cardinality::Keyed),
            false,
            false,
            none.clone(),
        );

        return FieldPlan {
            value: quote!(cx.resolve_keyed::<#value>().await),
            dependency: Some(dependency),
            check: None,
            wired: None,
        };
    }

    // Single edge. Peel the wrappers: `Option<…>` marks it optional, `Dynamic<…>`
    // runtime-provided; the remaining type is the `Injectable` handle to resolve.
    let (optional, after_option) = match attr::option_inner(ty) {
        Some(inner) => (true, inner),
        None => (false, ty.clone()),
    };
    let (dynamic, handle) = match attr::dynamic_inner(&after_option) {
        Some(inner) => (true, inner),
        None => (false, after_option),
    };

    let dep_name_str = match attr::arc_inner_type(&handle) {
        Ok(inner) => inner.to_token_stream().to_string(),
        Err(_) => handle.to_token_stream().to_string(),
    };
    let dep_name = LitStr::new(&dep_name_str, handle.span());

    let (resolved, qualifier) = match &field_qualifier {
        Some(q) => (
            quote!(cx.resolve_qualified::<#handle>(#q).await),
            quote!(::core::option::Option::Some(#q)),
        ),
        None => (quote!(cx.resolve::<#handle>().await), none),
    };
    let value = match (optional, dynamic) {
        (false, false) => quote!(#resolved.ok_or(#error::MissingComponent(#dep_name))?),
        (false, true) => quote!(#dynamic_ty(#resolved.ok_or(#error::MissingComponent(#dep_name))?)),
        (true, false) => resolved,
        (true, true) => quote!(#resolved.map(#dynamic_ty)),
    };

    let dependency = dep(
        &handle,
        quote!(#cardinality::One),
        optional,
        dynamic,
        qualifier,
    );

    // A required, concrete single edge is `Provide`-checkable; trait-object,
    // optional, and dynamic edges are not asserted in the per-macro path.
    let handle_is_trait = matches!(&handle, syn::Type::TraitObject(_))
        || matches!(attr::arc_inner_type(&handle), Ok(inner) if matches!(inner, syn::Type::TraitObject(_)));
    let target = quote!(<#handle as #injectable>::Target);

    // Eager concrete-only assert; lazy `Wired` includes trait objects too.
    let check = (!optional && !dynamic && !handle_is_trait).then(|| target.clone());
    let wired = (!optional && !dynamic).then_some(target);

    FieldPlan {
        value,
        dependency: Some(dependency),
        check,
        wired,
    }
}
