use crate::parsing::*;
use convert_case::{Case, Casing};
use proc_macro2::TokenStream as TokenStream2;
use quote::{ToTokens, format_ident, quote, quote_spanned};
use std::sync::atomic::AtomicU64;
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{Error, Expr, ExprPath, Ident, PatIdent, Token, WhereClause, WherePredicate, parse_quote};
static NODE_ID: AtomicU64 = AtomicU64::new(0);

pub(crate) fn generate_node_code(crate_ident: &CrateIdent, parsed: &ParsedNodeFn) -> syn::Result<TokenStream2> {
	let ParsedNodeFn {
		vis,
		attributes,
		fn_name,
		struct_name,
		mod_name,
		fn_generics,
		where_clause,
		input,
		output_type,
		is_async,
		fields,
		body,
		description,
		..
	} = parsed;
	let core_types = crate_ident.gcore()?;

	let category = attributes
		.category
		.as_ref()
		.expect("The 'category' attribute is required and should be checked during parsing, but was not found during codegen");
	let mod_name = format_ident!("_{}_mod", mod_name);

	let display_name = match &attributes.display_name.as_ref() {
		Some(lit) => lit.value(),
		None => struct_name.to_string().to_case(Case::Title),
	};
	let struct_name = format_ident!("{}Node", struct_name);

	// Separate data fields from regular fields
	let (data_fields, regular_fields): (Vec<_>, Vec<_>) = fields.iter().partition(|f| f.is_data_field);
	// The primary's position among the regular fields, which `#[scope]` fields may precede (`#[data]` fields are already partitioned out)
	let primary_regular_index = regular_fields.iter().position(|field| !field.is_environment());

	// Extract function generics used by data fields
	let data_field_generics: Vec<_> = fn_generics
		.iter()
		.filter(|generic| {
			let generic_ident = match generic {
				syn::GenericParam::Type(type_param) => &type_param.ident,
				_ => return false,
			};

			// Check if this generic is used in any data field type
			data_fields
				.iter()
				.any(|field| field.ty.regular().is_some_and(|regular| type_contains_ident(&regular.ty, generic_ident)))
		})
		.cloned()
		.collect();

	// Node generics for regular fields (Node0, Node1, ...)
	let node_generics: Vec<Ident> = regular_fields.iter().enumerate().map(|(i, _)| format_ident!("Node{}", i)).collect();

	// An `Item<T>` primary input declares the node as an element-wise rank-0 kernel over element type `T`
	let primary_element = primary_item_element(parsed);
	let element_wise = primary_element.is_some();
	let mapped_variant = generates_mapped_variant(parsed);
	let list_content_variant = generates_list_content_variant(parsed);

	// Extract just the idents from data_field_generics for struct type parameters
	let data_field_generic_idents: Vec<Ident> = data_field_generics
		.iter()
		.filter_map(|gp| match gp {
			syn::GenericParam::Type(tp) => Some(tp.ident.clone()),
			_ => None,
		})
		.collect();

	// Combined struct type parameters: data field generic idents (T, U, ...) + node generics (Node0, Node1, ...)
	// For struct type instantiation: MemoizeNode<T, Node0>
	let struct_type_params: Vec<Ident> = data_field_generic_idents.iter().cloned().chain(node_generics.iter().cloned()).collect();

	// Combined struct generic parameters with bounds for struct definition
	// struct MemoizeNode<T: Clone, Node0>
	let struct_generic_params: Vec<TokenStream2> = data_field_generics.iter().map(|gp| quote!(#gp)).chain(node_generics.iter().map(|id| quote!(#id))).collect();
	let input_ident = &input.pat_ident;

	let context_features = &input.context_features;

	// Regular field idents and names (for function parameters)
	let field_idents: Vec<_> = regular_fields.iter().map(|f| &f.pat_ident).collect();
	let field_names: Vec<_> = field_idents.iter().map(|pat_ident| &pat_ident.ident).collect();
	let regular_field_names: Vec<_> = regular_fields.iter().map(|f| &f.pat_ident.ident).collect();
	let data_field_names: Vec<_> = data_fields.iter().map(|f| &f.pat_ident.ident).collect();

	// Only regular fields have input names/descriptions (for UI)
	let input_names: Vec<_> = regular_fields
		.iter()
		.map(|f| &f.name)
		.zip(regular_field_names.iter())
		.map(|zipped| match zipped {
			(Some(name), _) => name.value(),
			(_, name) => name.to_string().to_case(Case::Title),
		})
		.collect();

	let input_hidden = regular_field_names.iter().map(|name| name.to_string().starts_with('_')).collect::<Vec<_>>();

	let input_descriptions: Vec<_> = regular_fields.iter().map(|f| &f.description).collect();

	// Generate struct fields: data fields (concrete types) + regular fields (generic types)
	let data_field_defs = data_fields.iter().map(|field| {
		let name = &field.pat_ident.ident;
		let Some(RegularParsedField { ty, .. }) = field.ty.regular() else {
			unreachable!("Data fields cannot be lazy `Node` inputs")
		};
		quote! { pub(super) #name: #ty }
	});

	let regular_field_defs = regular_field_names.iter().zip(node_generics.iter()).map(|(name, r#gen)| {
		quote! { pub(super) #name: #r#gen }
	});

	let struct_fields: Vec<_> = data_field_defs.chain(regular_field_defs).collect();

	let mut future_idents = Vec::new();

	// Data fields get passed as references to the underlying function
	let data_field_idents: Vec<_> = data_fields.iter().map(|f| &f.pat_ident).collect();
	let data_field_types: Vec<_> = data_fields
		.iter()
		.map(|field| {
			let Some(RegularParsedField { ty, .. }) = field.ty.regular() else {
				unreachable!("Data fields cannot be lazy `Node` inputs")
			};
			let ty = ty.clone();
			quote!(&#ty)
		})
		.collect();

	// Regular fields have types passed to the function
	let field_types: Vec<_> = regular_fields
		.iter()
		.map(|field| match &field.ty {
			ParsedFieldType::Node(NodeParsedField { output_type, input_type, .. }) => match parsed.is_async {
				true => parse_quote!(&'n impl #core_types::Node<'n, #input_type, Output = impl core::future::Future<Output=#output_type>>),
				false => parse_quote!(&'n impl #core_types::Node<'n, #input_type, Output = #output_type>),
			},
			value => value.regular().expect("a non-node field is a value field").ty.clone(),
		})
		.collect();

	// Only regular fields have UI metadata (data fields are internal state)
	let widget_override: Vec<_> = regular_fields
		.iter()
		.map(|field| match &field.widget_override {
			ParsedWidgetOverride::None => quote!(RegistryWidgetOverride::None),
			ParsedWidgetOverride::Hidden => quote!(RegistryWidgetOverride::Hidden),
			ParsedWidgetOverride::String(lit_str) => quote!(RegistryWidgetOverride::String(#lit_str)),
			ParsedWidgetOverride::Custom(lit_str) => quote!(RegistryWidgetOverride::Custom(#lit_str)),
		})
		.collect();

	let value_sources: Vec<_> = regular_fields
		.iter()
		.map(|field| match field.ty.regular() {
			Some(RegularParsedField { value_source, .. }) => match value_source {
				ParsedValueSource::Default(data) => {
					// Check if the data is a string literal by parsing the token stream
					let data_str = data.to_string();
					if data_str.starts_with('"') && data_str.ends_with('"') && data_str.len() >= 2 {
						quote!(RegistryValueSource::Default(#data))
					} else {
						quote!(RegistryValueSource::Default(stringify!(#data)))
					}
				}
				ParsedValueSource::Scope(data) => {
					if let syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(_), .. }) = data.as_ref() {
						quote!(RegistryValueSource::Scope(#data))
					} else {
						quote!(RegistryValueSource::Scope(#data.as_static_str()))
					}
				}
				_ => quote!(RegistryValueSource::None),
			},
			None => quote!(RegistryValueSource::None),
		})
		.collect();

	let default_colors: Vec<_> = regular_fields
		.iter()
		.map(|field| match field.ty.regular() {
			Some(RegularParsedField {
				value_source: ParsedValueSource::Default(data),
				..
			}) => match color_constant_paths(data) {
				Some(paths) => quote!(Some(&[#(#paths),*])),
				None => quote!(None),
			},
			_ => quote!(None),
		})
		.collect();

	let default_types: Vec<_> = regular_fields
		.iter()
		.enumerate()
		.map(|(index, field)| {
			let Some(RegularParsedField { implementations, value_source, .. }) = field.ty.regular() else {
				return quote!(None);
			};
			match implementations.first() {
				// A primary's scalar `#[default]` parses as a bare element (unranked, promoted at resolution); without one it defaults to an empty List
				Some(implementation_ty) if Some(index) == primary_regular_index && element_wise => match value_source {
					ParsedValueSource::Default(_) => quote!(Some(concrete!(#implementation_ty))),
					_ => quote!(Some(#core_types::list!(#implementation_ty))),
				},
				// A ranked implementation row emits structurally, keeping the element's TypeId which name-parsing in `normalize_rank` cannot recover
				Some(implementation_ty) => {
					if let Some(element) = peel_list(implementation_ty) {
						quote!(Some(#core_types::list!(#element)))
					} else if let Some(element) = peel_item(implementation_ty) {
						quote!(Some(#core_types::item!(#element, #element)))
					} else {
						quote!(Some(concrete!(#implementation_ty)))
					}
				}
				// A concrete ranked `Item<T>` param's scalar `#[default]` parses as a bare `T` literal (unranked, promoted at resolution);
				// without one it keeps the structural `Type::Item` wire type with the element's alias on its descriptor (so the rank-0 Properties widget still dispatches, e.g. `Progression`), and `node_inputs` peels to `T` if no `Item` type default exists
				None => match &field.ty {
					ParsedFieldType::Item {
						field: RegularParsedField { value_source, .. },
						element,
					} if !fn_generics
						.iter()
						.any(|generic| matches!(generic, syn::GenericParam::Type(param) if type_contains_ident(element, &param.ident))) =>
					{
						// The fn's lifetimes are elided since the metadata registration fn declares none of them
						let element = substitute_lifetimes(element.clone(), "_");
						match value_source {
							ParsedValueSource::Default(_) => quote!(Some(concrete!(#element))),
							_ => quote!(Some(#core_types::item!(#element, #element))),
						}
					}
					_ => quote!(None),
				},
			}
		})
		.collect();

	let bound_values = |select: fn(&RegularParsedField) -> &Option<NumberBound>| -> Vec<_> {
		regular_fields
			.iter()
			.map(|field| match field.ty.regular() {
				Some(regular) => select(regular).as_ref().map_or(quote!(None), |bound| quote!(Some(#bound))),
				None => quote!(None),
			})
			.collect()
	};
	let number_soft_min_values = bound_values(|field| &field.number_soft_min);
	let number_soft_max_values = bound_values(|field| &field.number_soft_max);
	let number_hard_min_values = bound_values(|field| &field.number_hard_min);
	let number_hard_max_values = bound_values(|field| &field.number_hard_max);
	let number_mode_range_values: Vec<_> = regular_fields
		.iter()
		.map(|field| match field.ty.regular() {
			Some(RegularParsedField { number_mode_range, .. }) => quote!(#number_mode_range),
			None => quote!(false),
		})
		.collect();
	let number_display_decimal_places: Vec<_> = regular_fields
		.iter()
		.map(|field| field.number_display_decimal_places.as_ref().map_or(quote!(None), |i| quote!(Some(#i))))
		.collect();
	let number_step: Vec<_> = regular_fields.iter().map(|field| field.number_step.as_ref().map_or(quote!(None), |i| quote!(Some(#i)))).collect();

	let unit_suffix: Vec<_> = regular_fields.iter().map(|field| field.unit.as_ref().map_or(quote!(None), |i| quote!(Some(#i)))).collect();

	let exposed: Vec<_> = regular_fields
		.iter()
		.map(|field| match field.ty.regular() {
			Some(RegularParsedField { exposed, .. }) => quote!(#exposed),
			None => quote!(true),
		})
		.collect();

	// Only eval regular fields (data fields are accessed directly as self.field_name)
	let eval_args = regular_fields.iter().map(|field| {
		let name = &field.pat_ident.ident;
		match &field.ty {
			ParsedFieldType::Node { .. } => {
				quote! { let #name = &self.#name; }
			}
			_ => {
				quote! { let #name = self.#name.eval(__input.clone()).await; }
			}
		}
	});

	// Only regular fields can have min/max constraints
	let min_max_args = regular_fields.iter().map(|field| match field.ty.regular() {
		Some(RegularParsedField { number_hard_min, number_hard_max, .. }) => {
			let name = &field.pat_ident.ident;
			let mut tokens = quote!();
			if let Some(min) = number_hard_min {
				tokens.extend(quote_spanned! {min.span()=>
					let #name = #core_types::misc::Clampable::clamp_hard_min(#name, #min);
				});
			}

			if let Some(max) = number_hard_max {
				tokens.extend(quote_spanned! {max.span()=>
					let #name = #core_types::misc::Clampable::clamp_hard_max(#name, #max);
				});
			}
			tokens
		}
		None => quote!(),
	});

	let all_implementation_types = fields.iter().flat_map(|field| match &field.ty {
		ParsedFieldType::Node(NodeParsedField { implementations, .. }) => implementations
			.iter()
			.flat_map(|implementation| [implementation.input.clone(), implementation.output.clone()])
			.collect::<Vec<_>>(),
		value => value.regular().map_or_else(Vec::new, |regular| regular.implementations.iter().cloned().collect()),
	});
	let all_implementation_types = all_implementation_types.chain(input.implementations.iter().cloned());

	let input_type = &parsed.input.ty;

	// Add Clampable bounds for fields with hard bounds, applying to each variant's evaluated wire type
	let build_clampable_clauses = |primary_wire: Option<WireWrapper>| -> Vec<TokenStream2> {
		regular_fields
			.iter()
			.filter_map(|field| {
				let RegularParsedField {
					ty, number_hard_min, number_hard_max, ..
				} = field.ty.regular()?;
				if number_hard_min.is_none() && number_hard_max.is_none() {
					return None;
				}

				let ty = match (&field.ty, primary_wire) {
					(ParsedFieldType::Item { element, .. }, Some(wrap)) if !field.is_environment() => wrap.apply(core_types, element),
					_ => ty.clone(),
				};
				Some(quote!(#ty: #core_types::misc::Clampable))
			})
			.collect()
	};
	future_idents.extend((0..regular_fields.len()).map(|id| format_ident!("F{}", id)));

	// Builds every field's where-clause bounds, wrapping each ranked connector's wire type for the element-wise variants
	// while environment fields keep their declared type. `list_content` additionally lifts a lazy primary connector's
	// `Item<E>` output to `List<E>` for the list-content variant.
	let build_field_clauses = |primary_wire: Option<WireWrapper>, list_content: bool| -> Vec<TokenStream2> {
		regular_fields
			.iter()
			.zip(node_generics.iter())
			.zip(future_idents.iter())
			.enumerate()
			.map(|(index, ((field, name), fut_ident))| match &field.ty {
				ParsedFieldType::Node(NodeParsedField {
					input_type,
					output_type,
					output_element,
					..
				}) => {
					if !*is_async {
						unreachable!("Found node which takes an impl Node<> input but is not async")
					}
					let output_type = if list_content && Some(index) == primary_regular_index {
						let element_ty = output_element.clone().unwrap_or_else(|| output_type.clone());
						WireWrapper::List.apply(core_types, &element_ty)
					} else {
						output_type.clone()
					};
					quote!(
						#fut_ident: core::future::Future<Output = #output_type> + #core_types::WasmNotSend + 'n,
						#name: #core_types::Node<'n, #input_type, Output = #fut_ident > + #core_types::WasmNotSync
					)
				}
				value => {
					let declared = &value.regular().expect("a non-node field is a value field").ty;
					let ty = match (value, primary_wire) {
						// An `Item<T>`-declared connector contributes its element type to the wire wrapping
						(ParsedFieldType::Item { element, .. }, Some(wrap)) if !field.is_environment() => wrap.apply(core_types, element),
						_ => declared.clone(),
					};
					let all_lifetime_ty = substitute_lifetimes(ty.clone(), "all");
					quote!(
						#fut_ident: core::future::Future<Output = #ty> + #core_types::WasmNotSend + 'n,
						for<'all> #all_lifetime_ty: #core_types::WasmNotSend,
						#name: #core_types::Node<'n, #input_type, Output = #fut_ident> + #core_types::WasmNotSync
					)
				}
			})
			.collect()
	};
	let where_clause = where_clause.clone().unwrap_or(WhereClause {
		where_token: Token![where](output_type.span()),
		predicates: Default::default(),
	});

	let make_struct_where_clause = |field_clauses: Vec<TokenStream2>, clampable_clauses: Vec<TokenStream2>, extra_clauses: Vec<TokenStream2>| {
		let mut struct_where_clause = where_clause.clone();
		let extra_where: Punctuated<WherePredicate, Comma> = parse_quote!(
			#(#field_clauses,)*
			#(#clampable_clauses,)*
			#(#extra_clauses,)*
			#output_type: 'n,
		);
		struct_where_clause.predicates.extend(extra_where);
		struct_where_clause
	};
	let primary_wire = element_wise.then_some(WireWrapper::Item);
	let struct_where_clause = make_struct_where_clause(build_field_clauses(primary_wire, false), build_clampable_clauses(primary_wire), Vec::new());

	// The mapped variant clones bare parameters and clones ranked connectors' items per frame slot, so both need Clone
	let param_clone_clauses: Vec<TokenStream2> = regular_fields
		.iter()
		.filter_map(|field| {
			let RegularParsedField { ty, .. } = field.ty.regular()?;
			Some(match &field.ty {
				ParsedFieldType::Item { element, .. } => quote!(#element: Clone),
				_ => quote!(#ty: Clone),
			})
		})
		.collect();
	let mapped_struct_where_clause = mapped_variant.then(|| {
		make_struct_where_clause(
			build_field_clauses(Some(WireWrapper::List), false),
			build_clampable_clauses(Some(WireWrapper::List)),
			param_clone_clauses.clone(),
		)
	});

	// The list-content variant clones each content slot and feeds it in behind a shared reference held across the kernel's await,
	// so the primary connector's element type needs `Clone` (to clone the slot) and `Sync` (so `&PrecomputedItemNode` is `Send`)
	let list_content_struct_where_clause = list_content_variant.then(|| {
		let mut extra_clauses = param_clone_clauses.clone();
		if let Some(content_element) = &primary_element {
			extra_clauses.push(quote!(#content_element: Clone + #core_types::WasmNotSync));
		}
		make_struct_where_clause(build_field_clauses(Some(WireWrapper::List), true), build_clampable_clauses(Some(WireWrapper::List)), extra_clauses)
	});

	// Only regular fields are parameters to new()
	let new_args: Vec<_> = node_generics
		.iter()
		.zip(regular_field_names.iter())
		.map(|(r#gen, name)| {
			quote! { #name: #r#gen }
		})
		.collect();

	// Initialize data fields with Default, regular fields with parameters
	let data_inits = data_field_names.iter().map(|name| {
		quote! { #name: Default::default() }
	});
	let regular_inits = regular_field_names.iter().map(|name| {
		quote! { #name }
	});
	let all_field_inits: Vec<_> = data_inits.chain(regular_inits).collect();

	let async_keyword = is_async.then(|| quote!(async));
	let await_keyword = is_async.then(|| quote!(.await));

	// Data fields may not implement Copy, PartialEq, etc., so only derive Debug and Clone
	let struct_derives = if data_fields.is_empty() {
		quote!(#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)])
	} else {
		quote!(#[derive(Debug, Clone)])
	};

	// Generate serialize method if serialize attribute is specified
	let serialize_impl = if let Some(serialize_fn) = &parsed.attributes.serialize {
		let data_field_refs = data_field_names.iter().map(|name| quote!(&self.#name));
		quote! {
			fn serialize(&self) -> Option<std::sync::Arc<dyn std::any::Any + Send + Sync>> {
				#serialize_fn(#(#data_field_refs),*)
			}
		}
	} else {
		quote!()
	};

	let eval_prelude = quote! {
		use #core_types::misc::Clampable;

		#(#eval_args)*
		#(#min_max_args)*
	};

	let eval_impl = quote! {
		type Output = #core_types::registry::DynFuture<'n, #output_type>;
		#[inline]
		fn eval(&'n self, __input: #input_type) -> Self::Output {
			Box::pin(async move {
				#eval_prelude
				self::#fn_name(__input #(, &self.#data_field_names)* #(, #regular_field_names)*) #await_keyword
			})
		}

		#serialize_impl
	};

	// The mapped variant zips every ranked connector by frame slot (longest-list, last-element repeats), broadcasting bare and environment parameters by clone
	let mapped_eval_impl = mapped_variant.then(|| {
		let ranked_names: Vec<_> = regular_fields
			.iter()
			.filter(|field| !field.is_environment() && matches!(&field.ty, ParsedFieldType::Item { .. }))
			.map(|field| &field.pat_ident.ident)
			.collect();

		let per_slot_args: Vec<_> = regular_fields
			.iter()
			.map(|field| {
				let name = &field.pat_ident.ident;
				match &field.ty {
					ParsedFieldType::Node(_) => quote!(#name),
					ParsedFieldType::Item { .. } if !field.is_environment() => quote! {
						#name.clone_item(__slot_index.min(#name.len() - 1)).expect("A zip slot index is always within bounds")
					},
					_ => quote!(#name.clone()),
				}
			})
			.collect();

		// The frame index is stamped onto each slot's context so lazy connectors can generate uniquely per slot.
		// A `()` generator frames purely over its param values (no stamp), so its kernel needs no context-extraction bounds.
		let has_lazy_connectors = regular_fields.iter().any(|field| matches!(&field.ty, ParsedFieldType::Node(_)));
		let slot_context = match has_lazy_connectors {
			true => quote!(#core_types::OwnedContextImpl::from(__input.clone()).with_index(__slot_index).into_context()),
			false => quote!(__input.clone()),
		};

		// An expander kernel (returning `List<U>`) flat-maps under the frame per the rank-2 force-flatten rule; a map kernel pushes one item per slot
		let (mapped_output_type, initial_output, collect_result) = match parsed.output_element.as_ref() {
			Some(element_ty) => (
				quote!(#core_types::list::List<#element_ty>),
				quote!(#core_types::list::List::with_capacity(__frame_length)),
				quote!(__output.push(__result);),
			),
			None => (quote!(#output_type), quote!(#core_types::list::List::new()), quote!(__output.extend(__result);)),
		};

		quote! {
			type Output = #core_types::registry::DynFuture<'n, #mapped_output_type>;
			#[inline]
			#[allow(clippy::clone_on_copy)]
			fn eval(&'n self, __input: #input_type) -> Self::Output {
				Box::pin(async move {
					#eval_prelude

					let __frame_length = [#(#ranked_names.len()),*].into_iter().max().unwrap_or(0);
					if [#(#ranked_names.len()),*].into_iter().any(|length| length == 0) {
						return #core_types::list::List::new();
					}

					let mut __output = #initial_output;
					for __slot_index in 0..__frame_length {
						let __slot_context = #slot_context;
						let __result = self::#fn_name(__slot_context #(, &self.#data_field_names)* #(, #per_slot_args)*) #await_keyword;
						#collect_result
					}

					__output
				})
			}

			#serialize_impl
		}
	});

	// The list-content variant evaluates a lazy primary's whole content `List` once, then feeds each slot into the kernel as a
	// precomputed stub (ambient footprint, no index stamp), zipping ranked params by slot with the frame taken from the content length.
	let list_content_eval_impl = list_content_variant.then(|| {
		let primary_index = primary_regular_index.expect("A list-content variant always has a lazy primary");
		let primary_name = &regular_fields[primary_index].pat_ident.ident;

		let ranked_names: Vec<_> = regular_fields
			.iter()
			.filter(|field| !field.is_environment() && matches!(&field.ty, ParsedFieldType::Item { .. }))
			.map(|field| &field.pat_ident.ident)
			.collect();

		let per_slot_args: Vec<_> = regular_fields
			.iter()
			.enumerate()
			.map(|(index, field)| {
				let name = &field.pat_ident.ident;
				if index == primary_index {
					return quote!(&__stub);
				}
				match &field.ty {
					ParsedFieldType::Node(_) => quote!(#name),
					ParsedFieldType::Item { .. } if !field.is_environment() => quote! {
						#name.clone_item(__slot_index.min(#name.len() - 1)).expect("A zip slot index is always within bounds")
					},
					_ => quote!(#name.clone()),
				}
			})
			.collect();

		let (list_content_output_type, initial_output, collect_result) = match parsed.output_element.as_ref() {
			Some(element_ty) => (
				quote!(#core_types::list::List<#element_ty>),
				quote!(#core_types::list::List::with_capacity(__frame_length)),
				quote!(__output.push(__result);),
			),
			None => (quote!(#output_type), quote!(#core_types::list::List::new()), quote!(__output.extend(__result);)),
		};

		let empty_param_check = (!ranked_names.is_empty()).then(|| {
			quote! {
				if [#(#ranked_names.len()),*].into_iter().any(|__length| __length == 0) {
					return #core_types::list::List::new();
				}
			}
		});

		quote! {
			type Output = #core_types::registry::DynFuture<'n, #list_content_output_type>;
			#[inline]
			#[allow(clippy::clone_on_copy)]
			fn eval(&'n self, __input: #input_type) -> Self::Output {
				Box::pin(async move {
					#eval_prelude

					let __content = #primary_name.eval(#core_types::OwnedContextImpl::from(__input.clone()).into_context()) #await_keyword;
					let __frame_length = __content.len();
					#empty_param_check

					let mut __output = #initial_output;
					for __slot_index in 0..__frame_length {
						let __stub = #core_types::value::PrecomputedItemNode::new(
							__content.clone_item(__slot_index).expect("A content slot index is always within bounds")
						);
						let __result = self::#fn_name(__input.clone() #(, &self.#data_field_names)* #(, #per_slot_args)*) #await_keyword;
						#collect_result
					}

					__output
				})
			}

			#serialize_impl
		}
	});

	let identifier = format_ident!("{}_proto_ident", fn_name);
	let identifier_path = match parsed.attributes.path.as_ref() {
		Some(path) => {
			let path = path.to_token_stream().to_string().replace(' ', "");
			quote!(#path)
		}
		None => quote!(std::module_path!()),
	};

	let mapped_struct_name = format_ident!("{}Mapped", struct_name);
	let list_content_struct_name = format_ident!("{}ListContent", struct_name);
	let register_node_impl = generate_register_node_impl(parsed, &field_names, &struct_name, &mapped_struct_name, &list_content_struct_name, &identifier)?;
	let import_name = format_ident!("_IMPORT_STUB_{}", mod_name.to_string().to_case(Case::UpperSnake));

	let properties = &attributes.properties_string.as_ref().map(|value| quote!(Some(#value))).unwrap_or(quote!(None));
	let memoize_flag = attributes.memoize;
	let inject_scope_flag = attributes.inject_scope;

	let cfg = crate::shader_nodes::modify_cfg(attributes);
	let node_input_accessor = generate_node_input_references(parsed, &field_idents, core_types, &identifier, &cfg);
	let ShaderTokens { shader_entry_point, gpu_node } = attributes.shader_node.as_ref().map(|n| n.codegen(crate_ident, parsed)).unwrap_or(Ok(ShaderTokens::default()))?;

	let mapped_node_impl = match (&mapped_struct_where_clause, &mapped_eval_impl) {
		(Some(mapped_where_clause), Some(mapped_eval)) => quote! {
			#cfg
			#[automatically_derived]
			impl<'n, #(#fn_generics,)* #(#node_generics,)* #(#future_idents,)*> #core_types::Node<'n, #input_type> for #mod_name::#mapped_struct_name<#(#struct_type_params,)*>
			#mapped_where_clause
			{
				#mapped_eval
			}
		},
		_ => quote!(),
	};

	let list_content_node_impl = match (&list_content_struct_where_clause, &list_content_eval_impl) {
		(Some(list_content_where_clause), Some(list_content_eval)) => quote! {
			#cfg
			#[automatically_derived]
			impl<'n, #(#fn_generics,)* #(#node_generics,)* #(#future_idents,)*> #core_types::Node<'n, #input_type> for #mod_name::#list_content_struct_name<#(#struct_type_params,)*>
			#list_content_where_clause
			{
				#list_content_eval
			}
		},
		_ => quote!(),
	};

	let mapped_struct_def = mapped_variant.then(|| {
		quote! {
			#struct_derives
			pub struct #mapped_struct_name<#(#struct_generic_params,)*> {
				#(#struct_fields,)*
			}

			#[automatically_derived]
			impl<'n, #(#struct_generic_params,)*> #mapped_struct_name<#(#struct_type_params,)*>
			{
				#[allow(clippy::too_many_arguments)]
				pub fn new(#(#new_args,)*) -> Self {
					Self {
						#(#all_field_inits,)*
					}
				}
			}
		}
	});

	let list_content_struct_def = list_content_variant.then(|| {
		quote! {
			#struct_derives
			pub struct #list_content_struct_name<#(#struct_generic_params,)*> {
				#(#struct_fields,)*
			}

			#[automatically_derived]
			impl<'n, #(#struct_generic_params,)*> #list_content_struct_name<#(#struct_type_params,)*>
			{
				#[allow(clippy::too_many_arguments)]
				pub fn new(#(#new_args,)*) -> Self {
					Self {
						#(#all_field_inits,)*
					}
				}
			}
		}
	});

	let mapped_struct_export = mapped_variant.then(|| {
		quote! {
			#cfg
			#[doc(hidden)]
			pub use #mod_name::#mapped_struct_name;
		}
	});

	let list_content_struct_export = list_content_variant.then(|| {
		quote! {
			#cfg
			#[doc(hidden)]
			pub use #mod_name::#list_content_struct_name;
		}
	});

	let display_name_header = format!("# {display_name}");
	let mut description_doc_attrs = vec![quote!(#[doc = #display_name_header]), quote!(#[doc = ""])];
	description_doc_attrs.extend(description.lines().map(|line| quote!(#[doc = #line])));

	// Add parameter list to doc comment
	if !input_names.is_empty() {
		description_doc_attrs.push(quote!(#[doc = ""]));
		description_doc_attrs.push(quote!(#[doc = "## Parameters"]));
		for (name, desc) in input_names.iter().zip(input_descriptions.iter()) {
			if desc.is_empty() {
				let header = format!("- **{name}**");
				description_doc_attrs.push(quote!(#[doc = #header]));
			} else {
				let first_line = desc.lines().next().unwrap_or("");
				let header = format!("- **{name}**: {first_line}");
				description_doc_attrs.push(quote!(#[doc = #header]));
				for line in desc.lines().skip(1) {
					let continuation = format!("  {line}");
					description_doc_attrs.push(quote!(#[doc = #continuation]));
				}
			}
		}
	}

	Ok(quote! {
		#(#description_doc_attrs)*
		#[inline]
		#[allow(clippy::too_many_arguments)]
		#vis #async_keyword fn #fn_name <'n, #(#fn_generics,)*> (#input_ident: #input_type #(, #data_field_idents: #data_field_types)* #(, #field_idents: #field_types)*) -> #output_type #where_clause #body

		#cfg
		#[automatically_derived]
		impl<'n, #(#fn_generics,)* #(#node_generics,)* #(#future_idents,)*> #core_types::Node<'n, #input_type> for #mod_name::#struct_name<#(#struct_type_params,)*>
		#struct_where_clause
		{
			#eval_impl
		}

		#mapped_node_impl

		#list_content_node_impl

		#cfg
		const fn #identifier() -> #core_types::ProtoNodeIdentifier {
			#core_types::ProtoNodeIdentifier::new(std::concat!(#identifier_path, "::", std::stringify!(#struct_name)))
		}

		#cfg
		#[doc(inline)]
		pub use #mod_name::#struct_name;

		#mapped_struct_export

		#list_content_struct_export

		#[doc(hidden)]
		#node_input_accessor

		#cfg
		#[doc(hidden)]
		#[allow(clippy::module_inception)]
		mod #mod_name {
			use super::*;
			use #core_types as gcore;
			use gcore::{Node, NodeIOTypes, concrete, fn_type, fn_type_fut, future, ProtoNodeIdentifier, WasmNotSync, NodeIO, ContextFeature};
			use gcore::value::ClonedNode;
			use gcore::ops::TypeNode;
			use gcore::registry::{NodeMetadata, FieldMetadata, NODE_REGISTRY, NODE_METADATA, DynAnyNode, DowncastBothNode, DynFuture, TypeErasedBox, PanicNode, RegistryValueSource, RegistryWidgetOverride};
			use gcore::ctor::ctor;

			// Use the types specified in the implementation

			static #import_name: core::marker::PhantomData<(#(#all_implementation_types,)*)> = core::marker::PhantomData;

			#struct_derives
			pub struct #struct_name<#(#struct_generic_params,)*> {
				#(#struct_fields,)*
			}

			#[automatically_derived]
			impl<'n, #(#struct_generic_params,)*> #struct_name<#(#struct_type_params,)*>
			{
				#[allow(clippy::too_many_arguments)]
				pub fn new(#(#new_args,)*) -> Self {
					Self {
						#(#all_field_inits,)*
					}
				}
			}

			#mapped_struct_def

			#list_content_struct_def

			#register_node_impl

			#[cfg_attr(not(target_family = "wasm"), ctor)]
			fn register_metadata() {
				let metadata = NodeMetadata {
					display_name: #display_name,
					category: #category,
					description: #description,
					properties: #properties,
					context_features: vec![#(ContextFeature::#context_features,)*],
					memoize: #memoize_flag,
					inject_scope: #inject_scope_flag,
					fields: vec![
						#(
							FieldMetadata {
								name: #input_names,
								widget_override: #widget_override,
								description: #input_descriptions,
								hidden: #input_hidden,
								exposed: #exposed,
								value_source: #value_sources,
								default_colors: #default_colors,
								default_type: #default_types,
								number_soft_min: #number_soft_min_values,
								number_soft_max: #number_soft_max_values,
								number_hard_min: #number_hard_min_values,
								number_hard_max: #number_hard_max_values,
								number_mode_range: #number_mode_range_values,
								number_display_decimal_places: #number_display_decimal_places,
								number_step: #number_step,
								unit: #unit_suffix,
							},
						)*
					],
				};
				NODE_METADATA.lock().unwrap().insert(#identifier(), metadata);
			}
		}

		#shader_entry_point

		#gpu_node
	})
}

/// The `Color::*` constant paths making up a default expression, when it consists solely of them (the form used by color and gradient parameter defaults).
fn color_constant_paths(tokens: &TokenStream2) -> Option<Vec<ExprPath>> {
	use syn::parse::Parser;

	let expressions = Punctuated::<Expr, Token![,]>::parse_terminated.parse2(tokens.clone()).ok()?;
	if expressions.is_empty() {
		return None;
	}

	expressions
		.into_iter()
		.map(|expression| {
			let Expr::Path(path) = expression else { return None };
			let segments = &path.path.segments;
			let is_color_constant = path.qself.is_none() && segments.len() == 2 && segments[0].ident == "Color" && segments.iter().all(|segment| segment.arguments.is_none());
			is_color_constant.then_some(path)
		})
		.collect()
}

/// Generates the per-parameter symbol types used to reference this node's inputs.
fn generate_node_input_references(parsed: &ParsedNodeFn, field_idents: &[&PatIdent], core_types: &TokenStream2, identifier: &Ident, cfg: &TokenStream2) -> TokenStream2 {
	let inputs_module_name = format_ident!("{}", parsed.struct_name.to_string().to_case(Case::Snake));

	let mut generated_input_accessor = Vec::new();
	if !parsed.attributes.skip_impl {
		for (input_index, input_ident) in field_idents.iter().enumerate() {
			// TODO: figure out a better name that doesn't conflict with so many types
			let struct_name = format_ident!("{}Input", input_ident.ident.to_string().to_case(Case::Pascal));

			// Every parameter gets a plain unit struct: the symbol used across the codebase to name this input
			generated_input_accessor.push(quote! {
				pub struct #struct_name;
				impl #core_types::NodeParameter for #struct_name {
					const NODE_IDENTIFIER: #core_types::ProtoNodeIdentifier = #inputs_module_name::IDENTIFIER;
					const INDEX: usize = #input_index;
				}
			});
		}
	}

	quote! {
		#cfg
		pub mod #inputs_module_name {
			use super::*;

			/// The `ProtoNodeIdentifier` of this node without any generics attached to it
			pub const IDENTIFIER: #core_types::ProtoNodeIdentifier = #identifier();
			#(#generated_input_accessor)*
		}
	}
}

/// The wire container a generated node variant is registered with, wrapping the kernel's primary input element type.
#[derive(Clone, Copy, PartialEq)]
enum WireWrapper {
	Item,
	List,
}

impl WireWrapper {
	fn apply(self, core_types: &TokenStream2, ty: &syn::Type) -> syn::Type {
		match self {
			WireWrapper::Item => parse_quote!(#core_types::list::Item<#ty>),
			WireWrapper::List => parse_quote!(#core_types::list::List<#ty>),
		}
	}
}

/// The variant of a node registered under one identifier: `Plain` for non-element-wise nodes, and the three element-wise wire
/// shapes (`Item` content and params, `Mapped` `List` params, `ListContent` `List` content for a lazy primary).
#[derive(Clone, Copy, PartialEq)]
enum RegisterVariant {
	Plain,
	Item,
	Mapped,
	ListContent,
}

impl RegisterVariant {
	/// The wrapper applied to ranked eager params for this variant.
	fn param_wrap(self) -> Option<WireWrapper> {
		match self {
			RegisterVariant::Item => Some(WireWrapper::Item),
			RegisterVariant::Mapped | RegisterVariant::ListContent => Some(WireWrapper::List),
			RegisterVariant::Plain => None,
		}
	}
}

/// Returns the element type of the node's primary input if it is declared `Item<T>` (directly, or as a lazy
/// connector's `Output = Item<T>`), which marks the node as element-wise.
fn primary_item_element(parsed: &ParsedNodeFn) -> Option<syn::Type> {
	// Manually registered nodes control their own variants
	if parsed.attributes.skip_impl {
		return None;
	}

	let (_, field) = parsed.primary_input_field()?;

	match &field.ty {
		ParsedFieldType::Node(NodeParsedField { output_element, .. }) => output_element.clone(),
		ParsedFieldType::Item { element, .. } => Some(element.clone()),
		_ => None,
	}
}

/// Whether the element-wise node gets a mapped `List` wire variant: an eager primary is its own frame source, while a lazy primary (or a `()` generator) draws the frame from its ranked eager params, so it needs at least one.
fn generates_mapped_variant(parsed: &ParsedNodeFn) -> bool {
	// A `()` generator frames over its ranked params exactly like a lazy primary, but generating fresh content per slot instead of transforming an upstream item
	if is_generator_frame(parsed) {
		return true;
	}

	if primary_item_element(parsed).is_none() {
		return false;
	}

	let mut input_fields = parsed.fields.iter().filter(|field| !field.is_environment());
	match input_fields.next().map(|field| &field.ty) {
		Some(ParsedFieldType::Node(_)) => input_fields.any(|field| matches!(&field.ty, ParsedFieldType::Item { .. })),
		_ => true,
	}
}

/// Whether the node is a `()`-primary generator that frames over its ranked params: a unit primary with at least one ranked (`Item<T>`) param.
fn is_generator_frame(parsed: &ParsedNodeFn) -> bool {
	if parsed.attributes.skip_impl {
		return false;
	}

	let Some((primary_index, primary)) = parsed.primary_input_field() else { return false };
	let Some(RegularParsedField { ty, .. }) = primary.ty.regular() else { return false };
	if !is_unit_type(ty) {
		return false;
	}

	parsed
		.fields
		.iter()
		.enumerate()
		.any(|(index, field)| index != primary_index && !field.is_environment() && matches!(&field.ty, ParsedFieldType::Item { .. }))
}

/// Whether the type is the unit type `()`, which marks a generator with no primary input.
fn is_unit_type(ty: &syn::Type) -> bool {
	matches!(ty, syn::Type::Tuple(tuple) if tuple.elems.is_empty())
}

/// Whether the element-wise node gets a list-content `List` wire variant: only a lazy primary connector qualifies, since it draws its frame from the whole content `List` (an eager primary already maps over `List` content via the mapped variant).
fn generates_list_content_variant(parsed: &ParsedNodeFn) -> bool {
	primary_item_element(parsed).is_some() && matches!(parsed.primary_input_field().map(|(_, field)| &field.ty), Some(ParsedFieldType::Node(_)))
}

fn generate_register_node_impl(
	parsed: &ParsedNodeFn,
	field_names: &[&Ident],
	struct_name: &Ident,
	mapped_struct_name: &Ident,
	list_content_struct_name: &Ident,
	identifier: &Ident,
) -> Result<TokenStream2, Error> {
	// On native, `register_node` and `register_metadata` run automatically via `#[ctor]`.
	// On Wasm, `ctor` isn't available, so this `extern "C"` fn is invoked from JS to register the same way.
	// `skip_impl` nodes don't generate a `register_node`, so the shim calls only `register_metadata` for them.
	let registry_name = format_ident!("__node_registry_{}_{}", NODE_ID.fetch_add(1, std::sync::atomic::Ordering::SeqCst), struct_name);
	let register_node_call = if parsed.attributes.skip_impl { quote!() } else { quote!(register_node();) };
	let wasm_shim = quote! {
		#[cfg(target_family = "wasm")]
		#[unsafe(no_mangle)]
		extern "C" fn #registry_name() {
			#register_node_call
			register_metadata();
		}
	};

	if parsed.attributes.skip_impl {
		return Ok(wasm_shim);
	}

	let mut constructors = Vec::new();
	let unit = parse_quote!(gcore::Context);

	let regular_fields: Vec<_> = parsed.fields.iter().filter(|f| !f.is_data_field).collect();
	let primary_regular_index = regular_fields.iter().position(|field| !field.is_environment());

	let parameter_types: Vec<_> = regular_fields
		.iter()
		.map(|field| {
			match &field.ty {
				ParsedFieldType::Node(NodeParsedField {
					implementations,
					input_type,
					output_type,
					..
				}) => {
					if !implementations.is_empty() {
						implementations.iter().map(|impl_| (&impl_.input, &impl_.output)).collect()
					} else {
						vec![(input_type, output_type)]
					}
				}
				value => {
					let RegularParsedField { implementations, ty, .. } = value.regular().expect("a non-node field is a value field");
					if !implementations.is_empty() {
						implementations.iter().map(|ty| (&unit, ty)).collect()
					} else {
						vec![(&unit, ty)]
					}
				}
			}
			.into_iter()
			.map(|(input, out)| (substitute_lifetimes(input.clone(), "_"), substitute_lifetimes(out.clone(), "_")))
			.collect::<Vec<_>>()
		})
		.collect();

	let max_implementations = parameter_types.iter().map(|x| x.len()).chain([parsed.input.implementations.len().max(1)]).max();

	// Element-wise nodes register a variant per wire shape per implementations row; all other nodes register one
	let gcore = quote!(gcore);
	let variants: Vec<RegisterVariant> = if primary_item_element(parsed).is_some() {
		let mut variants = vec![RegisterVariant::Item];
		if generates_mapped_variant(parsed) {
			variants.push(RegisterVariant::Mapped);
		}
		if generates_list_content_variant(parsed) {
			variants.push(RegisterVariant::ListContent);
		}
		variants
	} else if is_generator_frame(parsed) {
		// A `()` generator registers an Item form (single generation) plus a mapped form framed over its ranked params
		vec![RegisterVariant::Item, RegisterVariant::Mapped]
	} else {
		vec![RegisterVariant::Plain]
	};

	for i in 0..max_implementations.unwrap_or(0) {
		for &variant in &variants {
			let mut temp_constructors = Vec::new();
			let mut temp_node_io = Vec::new();
			let mut panic_node_types = Vec::new();

			for (j, types) in parameter_types.iter().enumerate() {
				let field_name = field_names[j];
				let (input_type, output_type) = &types[i.min(types.len() - 1)];
				// Rankedness comes from the field's declared type; its #[implementations(...)] entries are bare element types
				let field_is_ranked = !regular_fields[j].is_environment() && matches!(&regular_fields[j].ty, ParsedFieldType::Item { .. });
				let is_lazy_primary = Some(j) == primary_regular_index && matches!(regular_fields[j].ty, ParsedFieldType::Node { .. });
				// The list-content variant lifts its lazy primary connector's `Item<E>` content to `List<E>`; ranked params follow the variant's param wrap.
				// A ranked signature is spelled with bare `Item<...>`/`List<...>` tokens, which `fn_type_fut!` matches syntactically to construct the structural form.
				let (output_type, signature_type) = if is_lazy_primary && variant == RegisterVariant::ListContent {
					let element_ty = peel_item(output_type).unwrap_or_else(|| output_type.clone());
					(WireWrapper::List.apply(&gcore, &element_ty), Some(quote!(List<#element_ty>)))
				} else {
					match (field_is_ranked, variant.param_wrap()) {
						(true, Some(wrap)) => {
							let element_ty = peel_item(output_type).unwrap_or_else(|| output_type.clone());
							let signature = match wrap {
								WireWrapper::List => quote!(List<#element_ty>),
								WireWrapper::Item => quote!(Item<#element_ty>),
							};
							(wrap.apply(&gcore, &element_ty), Some(signature))
						}
						_ => (output_type.clone(), None),
					}
				};
				let signature_type = signature_type.unwrap_or_else(|| quote!(#output_type));

				let node = matches!(regular_fields[j].ty, ParsedFieldType::Node { .. });

				let downcast_node = quote!(
					let #field_name: DowncastBothNode<#input_type, #output_type> = DowncastBothNode::new(args[#j].clone());
				);
				if node && !parsed.is_async {
					return Err(Error::new_spanned(&parsed.fn_name, "Node needs to be async if you want to use lambda parameters"));
				}
				temp_constructors.push(downcast_node);
				temp_node_io.push(quote!(fn_type_fut!(#input_type, #signature_type, alias: #signature_type)));
				panic_node_types.push(quote!(#input_type, DynFuture<'static, #output_type>));
			}
			let input_type = match parsed.input.implementations.is_empty() {
				true => parsed.input.ty.clone(),
				false => parsed.input.implementations[i.min(parsed.input.implementations.len() - 1)].clone(),
			};
			let variant_struct_name = match variant {
				RegisterVariant::Mapped => mapped_struct_name,
				RegisterVariant::ListContent => list_content_struct_name,
				RegisterVariant::Item | RegisterVariant::Plain => struct_name,
			};
			constructors.push(quote!(
				(
					|args| {
						Box::pin(async move {
							#(#temp_constructors;)*
							let node = #variant_struct_name::new(#(#field_names,)*);
							// try polling futures
							let any: DynAnyNode<#input_type, _, _> = DynAnyNode::new(node);
							Box::new(any) as TypeErasedBox<'_>
						})
					}, {
						let node = #variant_struct_name::new(#(PanicNode::<#panic_node_types>::new(),)*);
						let params = vec![#(#temp_node_io,)*];
						let mut node_io = NodeIO::<'_, #input_type>::to_async_node_io(&node, params);
						node_io

					}
				)
			));
		}
	}
	let native = quote! {
	#[cfg_attr(not(target_family = "wasm"), ctor)]
	fn register_node() {
		let mut registry = NODE_REGISTRY.lock().unwrap();
		registry.insert(
			#identifier(),
			vec![
				#(#constructors,)*
			]
		);
	}
	};
	if cfg!(feature = "disable-registration") {
		return Ok(native);
	}

	Ok(quote! {
		#native
		#wasm_shim
	})
}

use crate::crate_ident::CrateIdent;
use crate::shader_nodes::{ShaderCodegen, ShaderTokens};
use syn::visit_mut::VisitMut;
use syn::{GenericArgument, Lifetime, Type};

struct LifetimeReplacer(&'static str);

impl VisitMut for LifetimeReplacer {
	fn visit_lifetime_mut(&mut self, lifetime: &mut Lifetime) {
		lifetime.ident = Ident::new(self.0, lifetime.ident.span());
	}

	fn visit_type_mut(&mut self, ty: &mut Type) {
		match ty {
			Type::Reference(type_reference) => {
				if let Some(lifetime) = &mut type_reference.lifetime {
					self.visit_lifetime_mut(lifetime);
				}
				self.visit_type_mut(&mut type_reference.elem);
			}
			_ => syn::visit_mut::visit_type_mut(self, ty),
		}
	}

	fn visit_generic_argument_mut(&mut self, arg: &mut GenericArgument) {
		if let GenericArgument::Lifetime(lifetime) = arg {
			self.visit_lifetime_mut(lifetime);
		} else {
			syn::visit_mut::visit_generic_argument_mut(self, arg);
		}
	}
}

#[must_use]
fn substitute_lifetimes(mut ty: Type, lifetime: &'static str) -> Type {
	LifetimeReplacer(lifetime).visit_type_mut(&mut ty);
	ty
}

/// Check if a type contains a reference to a specific identifier (e.g., a generic type parameter)
fn type_contains_ident(ty: &Type, ident: &Ident) -> bool {
	struct IdentChecker<'a> {
		target: &'a Ident,
		found: bool,
	}

	impl<'a, 'ast> syn::visit::Visit<'ast> for IdentChecker<'a> {
		fn visit_ident(&mut self, i: &'ast Ident) {
			if i == self.target {
				self.found = true;
			}
		}
	}

	let mut checker = IdentChecker { target: ident, found: false };
	syn::visit::visit_type(&mut checker, ty);
	checker.found
}
