elicitor_macro/
lib.rs

1//! Procedural macro for deriving `Survey` implementations.
2//!
3//! This crate provides the `#[derive(Survey)]` macro which generates:
4//! - `Survey` trait implementation
5//! - Type-specific builder with `suggest_*` and `assume_*` methods
6
7use proc_macro::TokenStream;
8use proc_macro2::TokenStream as TokenStream2;
9use quote::{format_ident, quote};
10use syn::{
11    Attribute, Data, DeriveInput, Expr, Fields, Ident, Lit, LitStr, Meta, Type, parse_macro_input,
12};
13
14/// Derive the `Survey` trait for a struct or enum.
15///
16/// # Attributes
17///
18/// ## On structs and enums
19/// - `#[prelude("...")]` - Message shown before the survey starts
20/// - `#[epilogue("...")]` - Message shown after the survey completes
21/// - `#[validate("fn_name")]` - Composite validator function
22/// - `#[validate_fields("fn_name")]` - Propagate a field-level validator to all numeric child fields
23///
24/// ## On fields
25/// - `#[ask("...")]` - The prompt text shown to the user (required for non-primitive types)
26/// - `#[mask]` - Hide input (for passwords)
27/// - `#[multiline]` - Open text editor / show textarea
28/// - `#[validate("fn_name")]` - Field-level validator function
29/// - `#[min(n)]` / `#[max(n)]` - Numeric bounds
30/// - `#[multiselect]` - For `Vec<Enum>` fields, enables multi-select
31#[proc_macro_derive(
32    Survey,
33    attributes(
34        ask,
35        mask,
36        multiline,
37        validate,
38        validate_fields,
39        min,
40        max,
41        prelude,
42        epilogue,
43        multiselect
44    )
45)]
46pub fn elicit(input: TokenStream) -> TokenStream {
47    let input = parse_macro_input!(input as DeriveInput);
48    implement_survey(&input)
49        .unwrap_or_else(|err| err.to_compile_error())
50        .into()
51}
52
53fn implement_survey(input: &DeriveInput) -> syn::Result<TokenStream2> {
54    let name = &input.ident;
55    let builder_name = format_ident!("{}Builder", name);
56
57    // Extract struct/enum level attributes
58    let type_attrs = TypeAttrs::extract(&input.attrs)?;
59
60    // Generate the survey() method
61    let survey_fn = generate_survey_fn(input, &type_attrs)?;
62
63    // Generate from_responses() method
64    let from_responses_fn = generate_from_responses_fn(input)?;
65
66    // Generate validate_field() method
67    let validate_field_fn = generate_validate_field_fn(input)?;
68
69    // Generate validate_all() method
70    let validate_all_fn = generate_validate_all_fn(input, &type_attrs)?;
71
72    // Generate the builder struct and its methods
73    let builder_impl = generate_builder(input, &builder_name)?;
74
75    // Generate validator compile-time checks
76    let validator_checks = generate_validator_checks(input)?;
77
78    // Generate typed field accessor methods
79    let field_accessors = generate_field_accessors(input)?;
80
81    // Generate ValidationContext struct for validators
82    let validation_context = generate_validation_context(input)?;
83
84    Ok(quote! {
85        #validator_checks
86
87        #validation_context
88
89        impl elicitor::Survey for #name {
90            fn survey() -> elicitor::SurveyDefinition {
91                #survey_fn
92            }
93
94            fn from_responses(responses: &elicitor::Responses) -> Self {
95                #from_responses_fn
96            }
97
98            fn validate_field(
99                value: &elicitor::ResponseValue,
100                responses: &elicitor::Responses,
101                path: &elicitor::ResponsePath,
102            ) -> Result<(), String> {
103                #validate_field_fn
104            }
105
106            fn validate_all(
107                responses: &elicitor::Responses,
108            ) -> std::collections::HashMap<elicitor::ResponsePath, String> {
109                #validate_all_fn
110            }
111        }
112
113        impl #name {
114            /// Returns a builder for running this survey.
115            pub fn builder() -> #builder_name {
116                #builder_name::new()
117            }
118
119            #field_accessors
120        }
121
122        #builder_impl
123    })
124}
125
126// ============================================================================
127// Attribute Extraction
128// ============================================================================
129
130/// Attributes that can appear on the struct/enum itself
131struct TypeAttrs {
132    prelude: Option<String>,
133    epilogue: Option<String>,
134    validate: Option<Ident>,
135    /// Validator to propagate to all numeric child fields
136    validate_fields: Option<Ident>,
137}
138
139impl TypeAttrs {
140    fn extract(attrs: &[Attribute]) -> syn::Result<Self> {
141        let mut prelude = None;
142        let mut epilogue = None;
143        let mut validate = None;
144        let mut validate_fields = None;
145
146        for attr in attrs {
147            if attr.path().is_ident("prelude") {
148                prelude = Some(extract_string_attr(attr)?);
149            } else if attr.path().is_ident("epilogue") {
150                epilogue = Some(extract_string_attr(attr)?);
151            } else if attr.path().is_ident("validate") {
152                validate = Some(extract_ident_attr(attr)?);
153            } else if attr.path().is_ident("validate_fields") {
154                validate_fields = Some(extract_ident_attr(attr)?);
155            }
156        }
157
158        Ok(Self {
159            prelude,
160            epilogue,
161            validate,
162            validate_fields,
163        })
164    }
165}
166
167/// Attributes that can appear on fields
168struct FieldAttrs {
169    ask: Option<String>,
170    mask: bool,
171    multiline: bool,
172    validate: Option<Ident>,
173    min: Option<i64>,
174    max: Option<i64>,
175    multiselect: bool,
176}
177
178impl FieldAttrs {
179    fn extract(attrs: &[Attribute]) -> syn::Result<Self> {
180        let mut ask = None;
181        let mut mask = false;
182        let mut multiline = false;
183        let mut validate = None;
184        let mut min = None;
185        let mut max = None;
186        let mut multiselect = false;
187
188        for attr in attrs {
189            if attr.path().is_ident("ask") {
190                ask = Some(extract_string_attr(attr)?);
191            } else if attr.path().is_ident("mask") {
192                mask = true;
193            } else if attr.path().is_ident("multiline") {
194                multiline = true;
195            } else if attr.path().is_ident("validate") {
196                validate = Some(extract_ident_attr(attr)?);
197            } else if attr.path().is_ident("min") {
198                min = Some(extract_int_attr(attr)?);
199            } else if attr.path().is_ident("max") {
200                max = Some(extract_int_attr(attr)?);
201            } else if attr.path().is_ident("multiselect") {
202                multiselect = true;
203            }
204        }
205
206        Ok(Self {
207            ask,
208            mask,
209            multiline,
210            validate,
211            min,
212            max,
213            multiselect,
214        })
215    }
216}
217
218fn extract_string_attr(attr: &Attribute) -> syn::Result<String> {
219    let meta = &attr.meta;
220    match meta {
221        Meta::List(list) => {
222            let lit: LitStr = list.parse_args()?;
223            Ok(lit.value())
224        }
225        _ => Err(syn::Error::new_spanned(
226            attr,
227            "expected #[attr(\"string\")]",
228        )),
229    }
230}
231
232fn extract_ident_attr(attr: &Attribute) -> syn::Result<Ident> {
233    let meta = &attr.meta;
234    match meta {
235        Meta::List(list) => {
236            // Try parsing as a string literal first (e.g., #[validate("fn_name")])
237            if let Ok(lit) = list.parse_args::<LitStr>() {
238                return Ok(Ident::new(&lit.value(), lit.span()));
239            }
240            // Then try parsing as an identifier (e.g., #[validate(fn_name)])
241            list.parse_args()
242        }
243        _ => Err(syn::Error::new_spanned(
244            attr,
245            "expected #[attr(identifier)] or #[attr(\"string\")]",
246        )),
247    }
248}
249
250fn extract_int_attr(attr: &Attribute) -> syn::Result<i64> {
251    let meta = &attr.meta;
252    match meta {
253        Meta::List(list) => {
254            let expr: Expr = list.parse_args()?;
255            match expr {
256                Expr::Lit(lit) => match &lit.lit {
257                    Lit::Int(int) => int.base10_parse(),
258                    _ => Err(syn::Error::new_spanned(lit, "expected integer literal")),
259                },
260                Expr::Unary(ref unary) => {
261                    if matches!(unary.op, syn::UnOp::Neg(_))
262                        && let Expr::Lit(ref lit) = *unary.expr
263                        && let Lit::Int(int) = &lit.lit
264                    {
265                        let val: i64 = int.base10_parse()?;
266                        return Ok(-val);
267                    }
268                    Err(syn::Error::new_spanned(expr, "expected integer literal"))
269                }
270                _ => Err(syn::Error::new_spanned(expr, "expected integer literal")),
271            }
272        }
273        _ => Err(syn::Error::new_spanned(attr, "expected #[attr(number)]")),
274    }
275}
276
277// ============================================================================
278// Survey Generation
279// ============================================================================
280
281fn generate_survey_fn(input: &DeriveInput, type_attrs: &TypeAttrs) -> syn::Result<TokenStream2> {
282    let prelude = match &type_attrs.prelude {
283        Some(s) => quote! { Some(#s.to_string()) },
284        None => quote! { None },
285    };
286
287    let epilogue = match &type_attrs.epilogue {
288        Some(s) => quote! { Some(#s.to_string()) },
289        None => quote! { None },
290    };
291
292    let questions = match &input.data {
293        Data::Struct(data) => generate_struct_questions(data, type_attrs.validate_fields.as_ref())?,
294        Data::Enum(data) => generate_enum_questions(data, &input.ident)?,
295        Data::Union(_) => {
296            return Err(syn::Error::new_spanned(
297                input,
298                "Survey cannot be derived for unions",
299            ));
300        }
301    };
302
303    Ok(quote! {
304        elicitor::SurveyDefinition {
305            prelude: #prelude,
306            questions: #questions,
307            epilogue: #epilogue,
308        }
309    })
310}
311
312fn generate_struct_questions(
313    data: &syn::DataStruct,
314    propagated_validator: Option<&Ident>,
315) -> syn::Result<TokenStream2> {
316    let mut questions = Vec::new();
317
318    match &data.fields {
319        Fields::Named(fields) => {
320            for field in &fields.named {
321                let field_name = field.ident.as_ref().unwrap();
322                let field_name_str = field_name.to_string();
323                let attrs = FieldAttrs::extract(&field.attrs)?;
324                let question = generate_question_for_field(
325                    &field_name_str,
326                    &field.ty,
327                    &attrs,
328                    propagated_validator,
329                )?;
330                questions.push(question);
331            }
332        }
333        Fields::Unnamed(fields) => {
334            for (i, field) in fields.unnamed.iter().enumerate() {
335                let field_name_str = i.to_string();
336                let attrs = FieldAttrs::extract(&field.attrs)?;
337                let question = generate_question_for_field(
338                    &field_name_str,
339                    &field.ty,
340                    &attrs,
341                    propagated_validator,
342                )?;
343                questions.push(question);
344            }
345        }
346        Fields::Unit => {}
347    }
348
349    Ok(quote! { vec![#(#questions),*] })
350}
351
352fn generate_enum_questions(data: &syn::DataEnum, _enum_name: &Ident) -> syn::Result<TokenStream2> {
353    // For enums, we generate a single OneOf question containing all variants
354    let mut variants = Vec::new();
355
356    for variant in &data.variants {
357        let variant_name = variant.ident.to_string();
358
359        // Check for #[ask] on the variant itself for display text
360        let variant_attrs = FieldAttrs::extract(&variant.attrs)?;
361        let display_name = variant_attrs.ask.unwrap_or_else(|| variant_name.clone());
362
363        let kind = match &variant.fields {
364            Fields::Unit => quote! { elicitor::QuestionKind::Unit },
365            Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
366                // Newtype variant - wrap in AllOf with a single Question to preserve prompt
367                let field = &fields.unnamed[0];
368                let attrs = FieldAttrs::extract(&field.attrs)?;
369                let q = generate_question_for_field("0", &field.ty, &attrs, None)?;
370                quote! { elicitor::QuestionKind::AllOf(elicitor::AllOfQuestion::new(vec![#q])) }
371            }
372            Fields::Unnamed(fields) => {
373                // Tuple variant - treat as AllOf
374                let mut qs = Vec::new();
375                for (i, field) in fields.unnamed.iter().enumerate() {
376                    let name = i.to_string();
377                    let attrs = FieldAttrs::extract(&field.attrs)?;
378                    let q = generate_question_for_field(&name, &field.ty, &attrs, None)?;
379                    qs.push(q);
380                }
381                quote! { elicitor::QuestionKind::AllOf(elicitor::AllOfQuestion::new(vec![#(#qs),*])) }
382            }
383            Fields::Named(fields) => {
384                // Struct variant
385                let mut qs = Vec::new();
386                for field in &fields.named {
387                    let name = field.ident.as_ref().unwrap().to_string();
388                    let attrs = FieldAttrs::extract(&field.attrs)?;
389                    let q = generate_question_for_field(&name, &field.ty, &attrs, None)?;
390                    qs.push(q);
391                }
392                quote! { elicitor::QuestionKind::AllOf(elicitor::AllOfQuestion::new(vec![#(#qs),*])) }
393            }
394        };
395
396        variants.push(quote! {
397            elicitor::Variant {
398                name: #display_name.to_string(),
399                kind: #kind,
400            }
401        });
402    }
403
404    // Return a single-element vec with the OneOf question
405    Ok(quote! {
406        vec![elicitor::Question::new(
407            elicitor::ResponsePath::empty(),
408            String::new(),  // No prompt for root enum
409            elicitor::QuestionKind::OneOf(elicitor::OneOfQuestion {
410                variants: vec![#(#variants),*],
411                default: None,
412            }),
413        )]
414    })
415}
416
417fn generate_question_for_field(
418    field_name: &str,
419    ty: &Type,
420    attrs: &FieldAttrs,
421    propagated_validator: Option<&Ident>,
422) -> syn::Result<TokenStream2> {
423    // Use field name as default prompt, converting snake_case to Title Case
424    let default_prompt = field_name
425        .split('_')
426        .map(|word| {
427            let mut chars = word.chars();
428            match chars.next() {
429                None => String::new(),
430                Some(first) => first.to_uppercase().chain(chars).collect(),
431            }
432        })
433        .collect::<Vec<_>>()
434        .join(" ");
435    let ask = attrs.ask.clone().unwrap_or(default_prompt);
436    let kind = generate_question_kind(ty, attrs, propagated_validator)?;
437
438    Ok(quote! {
439        elicitor::Question::new(
440            elicitor::ResponsePath::new(#field_name),
441            #ask.to_string(),
442            #kind,
443        )
444    })
445}
446
447fn generate_question_kind(
448    ty: &Type,
449    attrs: &FieldAttrs,
450    propagated_validator: Option<&Ident>,
451) -> syn::Result<TokenStream2> {
452    // Handle special attributes first
453    if attrs.mask {
454        let validate_opt = match (&attrs.validate, propagated_validator) {
455            (Some(v), _) => {
456                let v_str = v.to_string();
457                quote! { Some(#v_str.to_string()) }
458            }
459            (None, Some(v)) => {
460                let v_str = v.to_string();
461                quote! { Some(#v_str.to_string()) }
462            }
463            (None, None) => quote! { None },
464        };
465        return Ok(quote! {
466            elicitor::QuestionKind::Masked(elicitor::MaskedQuestion::with_validator(#validate_opt))
467        });
468    }
469
470    if attrs.multiline {
471        let validate_opt = match (&attrs.validate, propagated_validator) {
472            (Some(v), _) => {
473                let v_str = v.to_string();
474                quote! { Some(#v_str.to_string()) }
475            }
476            (None, Some(v)) => {
477                let v_str = v.to_string();
478                quote! { Some(#v_str.to_string()) }
479            }
480            (None, None) => quote! { None },
481        };
482        return Ok(quote! {
483            elicitor::QuestionKind::Multiline(elicitor::MultilineQuestion::with_validator(#validate_opt))
484        });
485    }
486
487    // Check for Vec<T>
488    if let Some(inner_ty) = extract_vec_inner_type(ty) {
489        // If multiselect is set, use AnyOf for Vec<Enum>
490        if attrs.multiselect {
491            return Ok(quote! {
492                elicitor::QuestionKind::AnyOf(elicitor::AnyOfQuestion {
493                    variants: <#inner_ty as elicitor::Survey>::survey()
494                        .questions
495                        .into_iter()
496                        .flat_map(|q| match q.kind() {
497                            elicitor::QuestionKind::OneOf(one_of) => one_of.variants.clone(),
498                            _ => vec![],
499                        })
500                        .collect(),
501                    defaults: vec![],
502                })
503            });
504        }
505
506        // For Vec<primitive>, generate ListQuestion
507        let inner_type_name = type_to_string(&inner_ty);
508        match inner_type_name.as_str() {
509            "String" => {
510                return Ok(quote! {
511                    elicitor::QuestionKind::List(elicitor::ListQuestion::strings())
512                });
513            }
514            "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => {
515                let min_opt = match attrs.min {
516                    Some(m) => quote! { Some(#m) },
517                    None => quote! { None },
518                };
519                let max_opt = match attrs.max {
520                    Some(m) => quote! { Some(#m) },
521                    None => quote! { None },
522                };
523                return Ok(quote! {
524                    elicitor::QuestionKind::List(elicitor::ListQuestion::ints_with_bounds(#min_opt, #max_opt))
525                });
526            }
527            "f32" | "f64" => {
528                let min_opt = match attrs.min {
529                    Some(m) => {
530                        let f = m as f64;
531                        quote! { Some(#f) }
532                    }
533                    None => quote! { None },
534                };
535                let max_opt = match attrs.max {
536                    Some(m) => {
537                        let f = m as f64;
538                        quote! { Some(#f) }
539                    }
540                    None => quote! { None },
541                };
542                return Ok(quote! {
543                    elicitor::QuestionKind::List(elicitor::ListQuestion::floats_with_bounds(#min_opt, #max_opt))
544                });
545            }
546            _ => {
547                // For other types (complex types without multiselect), fall through to error
548            }
549        }
550    }
551
552    // Handle types based on their name
553    let type_name = type_to_string(ty);
554
555    match type_name.as_str() {
556        "String" | "&str" => {
557            let validate_opt = match (&attrs.validate, propagated_validator) {
558                (Some(v), _) => {
559                    let v_str = v.to_string();
560                    quote! { Some(#v_str.to_string()) }
561                }
562                (None, Some(v)) => {
563                    let v_str = v.to_string();
564                    quote! { Some(#v_str.to_string()) }
565                }
566                (None, None) => quote! { None },
567            };
568            Ok(quote! {
569                elicitor::QuestionKind::Input(elicitor::InputQuestion::with_validator(#validate_opt))
570            })
571        }
572        "bool" => Ok(quote! {
573            elicitor::QuestionKind::Confirm(elicitor::ConfirmQuestion::new())
574        }),
575        "i8" | "i16" | "i32" | "i64" | "isize" | "u8" | "u16" | "u32" | "u64" | "usize" => {
576            let min_opt = match attrs.min {
577                Some(m) => quote! { Some(#m) },
578                None => quote! { None },
579            };
580            let max_opt = match attrs.max {
581                Some(m) => quote! { Some(#m) },
582                None => quote! { None },
583            };
584            // Use field-level validator if present, otherwise use propagated validator
585            let validate_opt = match (&attrs.validate, propagated_validator) {
586                (Some(v), _) => {
587                    let v_str = v.to_string();
588                    quote! { Some(#v_str.to_string()) }
589                }
590                (None, Some(v)) => {
591                    let v_str = v.to_string();
592                    quote! { Some(#v_str.to_string()) }
593                }
594                (None, None) => quote! { None },
595            };
596            Ok(quote! {
597                elicitor::QuestionKind::Int(elicitor::IntQuestion::with_bounds_and_validator(#min_opt, #max_opt, #validate_opt))
598            })
599        }
600        "f32" | "f64" => {
601            let min_opt = match attrs.min {
602                Some(m) => {
603                    let f = m as f64;
604                    quote! { Some(#f) }
605                }
606                None => quote! { None },
607            };
608            let max_opt = match attrs.max {
609                Some(m) => {
610                    let f = m as f64;
611                    quote! { Some(#f) }
612                }
613                None => quote! { None },
614            };
615            // Use field-level validator if present, otherwise use propagated validator
616            let validate_opt = match (&attrs.validate, propagated_validator) {
617                (Some(v), _) => {
618                    let v_str = v.to_string();
619                    quote! { Some(#v_str.to_string()) }
620                }
621                (None, Some(v)) => {
622                    let v_str = v.to_string();
623                    quote! { Some(#v_str.to_string()) }
624                }
625                (None, None) => quote! { None },
626            };
627            Ok(quote! {
628                elicitor::QuestionKind::Float(elicitor::FloatQuestion::with_bounds_and_validator(#min_opt, #max_opt, #validate_opt))
629            })
630        }
631        "PathBuf" => Ok(quote! {
632            elicitor::QuestionKind::Input(elicitor::InputQuestion::new())
633        }),
634        _ => {
635            // Check if it's an Option<T>
636            if let Some(inner_ty) = extract_option_inner_type(ty) {
637                let inner_kind = generate_question_kind(&inner_ty, attrs, propagated_validator)?;
638                // TODO: Handle Option properly - for now treat as inner type
639                return Ok(inner_kind);
640            }
641
642            // Assume it's a nested Survey type
643            Ok(quote! {
644                elicitor::QuestionKind::AllOf(
645                    elicitor::AllOfQuestion::new(<#ty as elicitor::Survey>::survey().questions)
646                )
647            })
648        }
649    }
650}
651
652fn type_to_string(ty: &Type) -> String {
653    match ty {
654        Type::Path(path) => path
655            .path
656            .segments
657            .last()
658            .map(|s| s.ident.to_string())
659            .unwrap_or_default(),
660        _ => String::new(),
661    }
662}
663
664fn extract_option_inner_type(ty: &Type) -> Option<Type> {
665    if let Type::Path(path) = ty
666        && let Some(segment) = path.path.segments.last()
667        && segment.ident == "Option"
668        && let syn::PathArguments::AngleBracketed(args) = &segment.arguments
669        && let Some(syn::GenericArgument::Type(inner)) = args.args.first()
670    {
671        return Some(inner.clone());
672    }
673    None
674}
675
676fn extract_vec_inner_type(ty: &Type) -> Option<Type> {
677    if let Type::Path(path) = ty
678        && let Some(segment) = path.path.segments.last()
679        && segment.ident == "Vec"
680        && let syn::PathArguments::AngleBracketed(args) = &segment.arguments
681        && let Some(syn::GenericArgument::Type(inner)) = args.args.first()
682    {
683        return Some(inner.clone());
684    }
685    None
686}
687
688// ============================================================================
689// from_responses Generation
690// ============================================================================
691
692fn generate_from_responses_fn(input: &DeriveInput) -> syn::Result<TokenStream2> {
693    match &input.data {
694        Data::Struct(data) => generate_from_responses_struct(&input.ident, data),
695        Data::Enum(data) => generate_from_responses_enum(&input.ident, data),
696        Data::Union(_) => Err(syn::Error::new_spanned(
697            input,
698            "Survey cannot be derived for unions",
699        )),
700    }
701}
702
703fn generate_from_responses_struct(
704    name: &Ident,
705    data: &syn::DataStruct,
706) -> syn::Result<TokenStream2> {
707    match &data.fields {
708        Fields::Named(fields) => {
709            let field_inits: Vec<_> = fields
710                .named
711                .iter()
712                .map(|f| {
713                    let field_name = f.ident.as_ref().unwrap();
714                    let field_name_str = field_name.to_string();
715                    let ty = &f.ty;
716                    let extraction = generate_value_extraction(&field_name_str, ty);
717                    quote! { #field_name: #extraction }
718                })
719                .collect();
720
721            Ok(quote! {
722                #name {
723                    #(#field_inits),*
724                }
725            })
726        }
727        Fields::Unnamed(fields) => {
728            let field_inits: Vec<_> = fields
729                .unnamed
730                .iter()
731                .enumerate()
732                .map(|(i, f)| {
733                    let field_name_str = i.to_string();
734                    let ty = &f.ty;
735                    generate_value_extraction(&field_name_str, ty)
736                })
737                .collect();
738
739            Ok(quote! {
740                #name(#(#field_inits),*)
741            })
742        }
743        Fields::Unit => Ok(quote! { #name }),
744    }
745}
746
747fn generate_from_responses_enum(name: &Ident, data: &syn::DataEnum) -> syn::Result<TokenStream2> {
748    let variant_arms: Vec<_> = data
749        .variants
750        .iter()
751        .enumerate()
752        .map(|(idx, variant)| {
753            let variant_name = &variant.ident;
754
755            let construction = match &variant.fields {
756                Fields::Unit => quote! { #name::#variant_name },
757                Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
758                    let ty = &fields.unnamed[0].ty;
759                    let extraction = generate_value_extraction("0", ty);
760                    quote! { #name::#variant_name(#extraction) }
761                }
762                Fields::Unnamed(fields) => {
763                    let extractions: Vec<_> = fields
764                        .unnamed
765                        .iter()
766                        .enumerate()
767                        .map(|(i, f)| {
768                            let ty = &f.ty;
769                            generate_value_extraction(&i.to_string(), ty)
770                        })
771                        .collect();
772                    quote! { #name::#variant_name(#(#extractions),*) }
773                }
774                Fields::Named(fields) => {
775                    let field_inits: Vec<_> = fields
776                        .named
777                        .iter()
778                        .map(|f| {
779                            let field_name = f.ident.as_ref().unwrap();
780                            let field_name_str = field_name.to_string();
781                            let ty = &f.ty;
782                            let extraction = generate_value_extraction(&field_name_str, ty);
783                            quote! { #field_name: #extraction }
784                        })
785                        .collect();
786                    quote! { #name::#variant_name { #(#field_inits),* } }
787                }
788            };
789
790            quote! { #idx => #construction }
791        })
792        .collect();
793
794    Ok(quote! {
795        let variant_idx = responses
796            .get_chosen_variant(&elicitor::ResponsePath::new(elicitor::SELECTED_VARIANT_KEY))
797            .expect("missing selected_variant");
798        match variant_idx {
799            #(#variant_arms,)*
800            _ => panic!("invalid variant index"),
801        }
802    })
803}
804
805fn generate_value_extraction(field_name: &str, ty: &Type) -> TokenStream2 {
806    let type_name = type_to_string(ty);
807    let path_expr = quote! { elicitor::ResponsePath::new(#field_name) };
808
809    match type_name.as_str() {
810        "String" => quote! {
811            responses.get_string(&#path_expr).expect("missing string").to_string()
812        },
813        "bool" => quote! {
814            responses.get_bool(&#path_expr).expect("missing bool")
815        },
816        "i8" | "i16" | "i32" | "i64" | "isize" => quote! {
817            responses.get_int(&#path_expr).expect("missing int") as #ty
818        },
819        "u8" | "u16" | "u32" | "u64" | "usize" => quote! {
820            responses.get_int(&#path_expr).expect("missing int") as #ty
821        },
822        "f32" | "f64" => quote! {
823            responses.get_float(&#path_expr).expect("missing float") as #ty
824        },
825        "PathBuf" => quote! {
826            std::path::PathBuf::from(responses.get_string(&#path_expr).expect("missing path"))
827        },
828        _ => {
829            // Check for Option<T>
830            if let Some(inner_ty) = extract_option_inner_type(ty) {
831                let inner_extraction = generate_value_extraction(field_name, &inner_ty);
832                return quote! {
833                    if responses.has_value(&#path_expr) {
834                        Some(#inner_extraction)
835                    } else {
836                        None
837                    }
838                };
839            }
840
841            // Check for Vec<T>
842            if let Some(inner_ty) = extract_vec_inner_type(ty) {
843                let inner_type_name = type_to_string(&inner_ty);
844
845                // For primitive types, use the list response values
846                match inner_type_name.as_str() {
847                    "String" => {
848                        return quote! {
849                            responses.get_string_list(&#path_expr)
850                                .expect("missing string list")
851                                .to_vec()
852                        };
853                    }
854                    "i8" | "i16" | "i32" | "i64" | "isize" => {
855                        return quote! {
856                            responses.get_int_list(&#path_expr)
857                                .expect("missing int list")
858                                .iter()
859                                .map(|&n| n as #inner_ty)
860                                .collect()
861                        };
862                    }
863                    "u8" | "u16" | "u32" | "u64" | "usize" => {
864                        return quote! {
865                            responses.get_int_list(&#path_expr)
866                                .expect("missing int list")
867                                .iter()
868                                .map(|&n| n as #inner_ty)
869                                .collect()
870                        };
871                    }
872                    "f32" | "f64" => {
873                        return quote! {
874                            responses.get_float_list(&#path_expr)
875                                .expect("missing float list")
876                                .iter()
877                                .map(|&n| n as #inner_ty)
878                                .collect()
879                        };
880                    }
881                    _ => {
882                        // For complex types (enums with multiselect), use chosen_variants
883                        let variants_path = quote! {
884                            elicitor::ResponsePath::new(
885                                &format!("{}.{}", #field_name, elicitor::SELECTED_VARIANTS_KEY)
886                            )
887                        };
888                        return quote! {
889                            {
890                                let indices = responses
891                                    .get_chosen_variants(&#variants_path)
892                                    .map(|s| s.to_vec())
893                                    .unwrap_or_default();
894
895                                // Reconstruct each item from its indexed responses
896                                indices
897                                    .iter()
898                                    .enumerate()
899                                    .map(|(item_idx, _variant_idx)| {
900                                        let item_prefix = elicitor::ResponsePath::new(
901                                            &format!("{}.{}", #field_name, item_idx)
902                                        );
903                                        let item_responses = responses.filter_prefix(&item_prefix);
904                                        <#inner_ty as elicitor::Survey>::from_responses(&item_responses)
905                                    })
906                                    .collect()
907                            }
908                        };
909                    }
910                }
911            }
912
913            // Nested Survey type - filter responses and call its from_responses
914            quote! {
915                {
916                    let prefix = elicitor::ResponsePath::new(#field_name);
917                    let nested_responses = responses.filter_prefix(&prefix);
918                    <#ty as elicitor::Survey>::from_responses(&nested_responses)
919                }
920            }
921        }
922    }
923}
924
925// ============================================================================
926// Field Accessor Generation
927// ============================================================================
928
929/// Generate typed field accessor methods for retrieving values from Responses.
930/// These methods allow validators to access field values without using string paths.
931fn generate_field_accessors(input: &DeriveInput) -> syn::Result<TokenStream2> {
932    let mut accessors = Vec::new();
933
934    match &input.data {
935        Data::Struct(data) => {
936            if let Fields::Named(fields) = &data.fields {
937                for field in &fields.named {
938                    let field_name = field.ident.as_ref().unwrap();
939                    let field_name_str = field_name.to_string();
940                    let ty = &field.ty;
941
942                    let accessor = generate_field_accessor_method(&field_name_str, field_name, ty);
943                    accessors.push(accessor);
944                }
945            }
946        }
947        Data::Enum(_) => {
948            // Enums don't need field accessors in the same way
949        }
950        Data::Union(_) => {}
951    }
952
953    Ok(quote! {
954        #(#accessors)*
955    })
956}
957
958/// Generate a single field accessor method
959fn generate_field_accessor_method(
960    field_name_str: &str,
961    field_name: &Ident,
962    ty: &Type,
963) -> TokenStream2 {
964    let method_name = format_ident!("get_{}", field_name);
965    let type_name = type_to_string(ty);
966    let path_expr = quote! { elicitor::ResponsePath::new(#field_name_str) };
967
968    match type_name.as_str() {
969        "String" => quote! {
970            /// Get the value of this field from responses, if present.
971            pub fn #method_name(responses: &elicitor::Responses) -> Option<String> {
972                responses.get_string(&#path_expr).ok().map(|s| s.to_string())
973            }
974        },
975        "bool" => quote! {
976            /// Get the value of this field from responses, if present.
977            pub fn #method_name(responses: &elicitor::Responses) -> Option<bool> {
978                responses.get_bool(&#path_expr).ok()
979            }
980        },
981        "i8" | "i16" | "i32" | "i64" | "isize" => quote! {
982            /// Get the value of this field from responses, if present.
983            pub fn #method_name(responses: &elicitor::Responses) -> Option<#ty> {
984                responses.get_int(&#path_expr).ok().map(|n| n as #ty)
985            }
986        },
987        "u8" | "u16" | "u32" | "u64" | "usize" => quote! {
988            /// Get the value of this field from responses, if present.
989            pub fn #method_name(responses: &elicitor::Responses) -> Option<#ty> {
990                responses.get_int(&#path_expr).ok().map(|n| n as #ty)
991            }
992        },
993        "f32" | "f64" => quote! {
994            /// Get the value of this field from responses, if present.
995            pub fn #method_name(responses: &elicitor::Responses) -> Option<#ty> {
996                responses.get_float(&#path_expr).ok().map(|n| n as #ty)
997            }
998        },
999        "PathBuf" => quote! {
1000            /// Get the value of this field from responses, if present.
1001            pub fn #method_name(responses: &elicitor::Responses) -> Option<std::path::PathBuf> {
1002                responses.get_string(&#path_expr).ok().map(std::path::PathBuf::from)
1003            }
1004        },
1005        _ => {
1006            // For complex types (nested structs, enums, etc.), we don't generate accessors
1007            // as they would require more complex handling
1008            quote! {}
1009        }
1010    }
1011}
1012
1013// ============================================================================
1014// ValidationContext Generation
1015// ============================================================================
1016
1017/// Generate a ValidationContext struct for accessing sibling fields during validation.
1018/// This struct wraps Responses and a prefix path, providing typed accessor methods.
1019fn generate_validation_context(input: &DeriveInput) -> syn::Result<TokenStream2> {
1020    let name = &input.ident;
1021    let context_name = format_ident!("{}ValidationContext", name);
1022
1023    let mut accessors = Vec::new();
1024
1025    match &input.data {
1026        Data::Struct(data) => {
1027            if let Fields::Named(fields) = &data.fields {
1028                for field in &fields.named {
1029                    let field_name = field.ident.as_ref().unwrap();
1030                    let field_name_str = field_name.to_string();
1031                    let ty = &field.ty;
1032
1033                    let accessor =
1034                        generate_context_accessor_method(&field_name_str, field_name, ty);
1035                    accessors.push(accessor);
1036                }
1037            }
1038        }
1039        Data::Enum(_) => {
1040            // Enums don't need field accessors in the same way
1041        }
1042        Data::Union(_) => {}
1043    }
1044
1045    Ok(quote! {
1046        /// Validation context for #name, providing access to sibling fields.
1047        pub struct #context_name<'a> {
1048            responses: &'a elicitor::Responses,
1049            prefix: elicitor::ResponsePath,
1050        }
1051
1052        impl<'a> #context_name<'a> {
1053            /// Create a new validation context with the given prefix path.
1054            pub fn new(responses: &'a elicitor::Responses, prefix: elicitor::ResponsePath) -> Self {
1055                Self { responses, prefix }
1056            }
1057
1058            /// Get the prefix path for this context.
1059            pub fn prefix(&self) -> &elicitor::ResponsePath {
1060                &self.prefix
1061            }
1062
1063            /// Get the underlying responses.
1064            pub fn responses(&self) -> &elicitor::Responses {
1065                self.responses
1066            }
1067
1068            #(#accessors)*
1069        }
1070    })
1071}
1072
1073/// Generate a single accessor method for ValidationContext
1074fn generate_context_accessor_method(
1075    field_name_str: &str,
1076    field_name: &Ident,
1077    ty: &Type,
1078) -> TokenStream2 {
1079    let method_name = format_ident!("get_{}", field_name);
1080    let type_name = type_to_string(ty);
1081
1082    match type_name.as_str() {
1083        "String" => quote! {
1084            /// Get the value of this field from responses, if present.
1085            pub fn #method_name(&self) -> Option<String> {
1086                let path = self.prefix.child(#field_name_str);
1087                self.responses.get_string(&path).ok().map(|s| s.to_string())
1088            }
1089        },
1090        "bool" => quote! {
1091            /// Get the value of this field from responses, if present.
1092            pub fn #method_name(&self) -> Option<bool> {
1093                let path = self.prefix.child(#field_name_str);
1094                self.responses.get_bool(&path).ok()
1095            }
1096        },
1097        "i8" | "i16" | "i32" | "i64" | "isize" => quote! {
1098            /// Get the value of this field from responses, if present.
1099            pub fn #method_name(&self) -> Option<#ty> {
1100                let path = self.prefix.child(#field_name_str);
1101                self.responses.get_int(&path).ok().map(|n| n as #ty)
1102            }
1103        },
1104        "u8" | "u16" | "u32" | "u64" | "usize" => quote! {
1105            /// Get the value of this field from responses, if present.
1106            pub fn #method_name(&self) -> Option<#ty> {
1107                let path = self.prefix.child(#field_name_str);
1108                self.responses.get_int(&path).ok().map(|n| n as #ty)
1109            }
1110        },
1111        "f32" | "f64" => quote! {
1112            /// Get the value of this field from responses, if present.
1113            pub fn #method_name(&self) -> Option<#ty> {
1114                let path = self.prefix.child(#field_name_str);
1115                self.responses.get_float(&path).ok().map(|n| n as #ty)
1116            }
1117        },
1118        "PathBuf" => quote! {
1119            /// Get the value of this field from responses, if present.
1120            pub fn #method_name(&self) -> Option<std::path::PathBuf> {
1121                let path = self.prefix.child(#field_name_str);
1122                self.responses.get_string(&path).ok().map(std::path::PathBuf::from)
1123            }
1124        },
1125        _ => {
1126            // For complex types, don't generate accessors
1127            quote! {}
1128        }
1129    }
1130}
1131
1132// ============================================================================
1133// Validation Generation
1134// ============================================================================
1135
1136fn generate_validate_field_fn(input: &DeriveInput) -> syn::Result<TokenStream2> {
1137    let mut validators = Vec::new();
1138
1139    // Note: min/max validation is NOT generated here because:
1140    // 1. It's stored in IntQuestion/FloatQuestion and handled by backends directly
1141    // 2. This function runs ALL validators for ALL fields, so field-specific
1142    //    min/max checks would incorrectly apply to other fields
1143
1144    // Helper to check if a path matches a field name
1145    // The path could be "field_name" or "parent.field_name" etc.
1146    let path_matches_field = |field_name: &str| -> TokenStream2 {
1147        quote! {
1148            (path.as_str() == #field_name || path.as_str().ends_with(&format!(".{}", #field_name)))
1149        }
1150    };
1151
1152    // Collect field names for path checking in validate_fields
1153    let field_names: Vec<String> = match &input.data {
1154        Data::Struct(data) => {
1155            if let Fields::Named(fields) = &data.fields {
1156                fields
1157                    .named
1158                    .iter()
1159                    .filter_map(|f| f.ident.as_ref().map(|i| i.to_string()))
1160                    .collect()
1161            } else {
1162                vec![]
1163            }
1164        }
1165        Data::Enum(_) => vec![],
1166        _ => vec![],
1167    };
1168
1169    // Add the propagated validator (from #[validate_fields]) if present
1170    // Only run for paths that match one of this struct's fields
1171    let type_attrs = TypeAttrs::extract(&input.attrs)?;
1172    if let Some(validator) = &type_attrs.validate_fields {
1173        if !field_names.is_empty() {
1174            // Generate path checks for all fields in this struct
1175            let field_checks: Vec<TokenStream2> = field_names
1176                .iter()
1177                .map(|name| path_matches_field(name))
1178                .collect();
1179
1180            validators.push(quote! {
1181                // Only run validate_fields if the path matches one of this struct's fields
1182                if #(#field_checks)||* {
1183                    if let Err(e) = #validator(value, responses, path) {
1184                        return Err(e);
1185                    }
1186                }
1187            });
1188        } else {
1189            // Fallback for non-struct types (shouldn't normally happen)
1190            validators.push(quote! {
1191                if let Err(e) = #validator(value, responses, path) {
1192                    return Err(e);
1193                }
1194            });
1195        }
1196    }
1197
1198    match &input.data {
1199        Data::Struct(data) => {
1200            if let Fields::Named(fields) = &data.fields {
1201                for field in &fields.named {
1202                    let attrs = FieldAttrs::extract(&field.attrs)?;
1203                    let ty = &field.ty;
1204                    let field_name = field.ident.as_ref().unwrap().to_string();
1205
1206                    if let Some(validator) = &attrs.validate {
1207                        let path_check = path_matches_field(&field_name);
1208                        // Only run this validator if the path matches this field
1209                        validators.push(quote! {
1210                            if #path_check {
1211                                if let Err(e) = #validator(value, responses, path) {
1212                                    return Err(e);
1213                                }
1214                            }
1215                        });
1216                    }
1217
1218                    // Delegate to nested Survey types for validation
1219                    let type_name = type_to_string(ty);
1220                    let is_primitive = matches!(
1221                        type_name.as_str(),
1222                        "String"
1223                            | "&str"
1224                            | "bool"
1225                            | "i8"
1226                            | "i16"
1227                            | "i32"
1228                            | "i64"
1229                            | "isize"
1230                            | "u8"
1231                            | "u16"
1232                            | "u32"
1233                            | "u64"
1234                            | "usize"
1235                            | "f32"
1236                            | "f64"
1237                            | "PathBuf"
1238                    );
1239
1240                    // Skip Vec types (they're handled differently) and primitives
1241                    if !is_primitive
1242                        && extract_vec_inner_type(ty).is_none()
1243                        && extract_option_inner_type(ty).is_none()
1244                    {
1245                        validators.push(quote! {
1246                            // Delegate validation to nested Survey type
1247                            if let Err(e) = <#ty as elicitor::Survey>::validate_field(value, responses, path) {
1248                                return Err(e);
1249                            }
1250                        });
1251                    }
1252                }
1253            }
1254        }
1255        Data::Enum(data) => {
1256            for variant in &data.variants {
1257                match &variant.fields {
1258                    Fields::Named(fields) => {
1259                        for field in &fields.named {
1260                            let attrs = FieldAttrs::extract(&field.attrs)?;
1261                            let field_name = field.ident.as_ref().unwrap().to_string();
1262
1263                            if let Some(validator) = &attrs.validate {
1264                                let path_check = path_matches_field(&field_name);
1265                                validators.push(quote! {
1266                                    if #path_check {
1267                                        if let Err(e) = #validator(value, responses, path) {
1268                                            return Err(e);
1269                                        }
1270                                    }
1271                                });
1272                            }
1273                        }
1274                    }
1275                    Fields::Unnamed(fields) => {
1276                        // For unnamed fields (tuple variants), use the variant name as identifier
1277                        let variant_name = variant.ident.to_string();
1278                        for (idx, field) in fields.unnamed.iter().enumerate() {
1279                            let attrs = FieldAttrs::extract(&field.attrs)?;
1280                            // For tuple variants, the path ends with the variant name
1281                            let field_name = if fields.unnamed.len() == 1 {
1282                                variant_name.clone()
1283                            } else {
1284                                format!("{}.{}", variant_name, idx)
1285                            };
1286
1287                            if let Some(validator) = &attrs.validate {
1288                                let path_check = path_matches_field(&field_name);
1289                                validators.push(quote! {
1290                                    if #path_check {
1291                                        if let Err(e) = #validator(value, responses, path) {
1292                                            return Err(e);
1293                                        }
1294                                    }
1295                                });
1296                            }
1297                        }
1298                    }
1299                    Fields::Unit => {}
1300                }
1301            }
1302        }
1303        Data::Union(_) => {}
1304    }
1305
1306    Ok(quote! {
1307        #(#validators)*
1308        Ok(())
1309    })
1310}
1311
1312fn generate_validate_all_fn(
1313    _input: &DeriveInput,
1314    type_attrs: &TypeAttrs,
1315) -> syn::Result<TokenStream2> {
1316    let composite_call = if let Some(validator) = &type_attrs.validate {
1317        quote! { #validator(responses) }
1318    } else {
1319        quote! { std::collections::HashMap::new() }
1320    };
1321
1322    // For now, just call the composite validator if present
1323    // TODO: Also validate nested Survey types
1324    Ok(quote! {
1325        #composite_call
1326    })
1327}
1328
1329fn generate_validator_checks(input: &DeriveInput) -> syn::Result<TokenStream2> {
1330    let mut checks = Vec::new();
1331
1332    // Check composite validator
1333    let type_attrs = TypeAttrs::extract(&input.attrs)?;
1334    if let Some(validator) = &type_attrs.validate {
1335        checks.push(quote! {
1336            const _: fn(&elicitor::Responses) -> std::collections::HashMap<elicitor::ResponsePath, String> = #validator;
1337        });
1338    }
1339
1340    // Check propagated field validator (validate_fields)
1341    if let Some(validator) = &type_attrs.validate_fields {
1342        checks.push(quote! {
1343            const _: fn(&elicitor::ResponseValue, &elicitor::Responses, &elicitor::ResponsePath) -> Result<(), String> = #validator;
1344        });
1345    }
1346
1347    // Check field validators
1348    let check_field = |field: &syn::Field, checks: &mut Vec<TokenStream2>| -> syn::Result<()> {
1349        let attrs = FieldAttrs::extract(&field.attrs)?;
1350        if let Some(validator) = &attrs.validate {
1351            checks.push(quote! {
1352                const _: fn(&elicitor::ResponseValue, &elicitor::Responses, &elicitor::ResponsePath) -> Result<(), String> = #validator;
1353            });
1354        }
1355        Ok(())
1356    };
1357
1358    match &input.data {
1359        Data::Struct(data) => {
1360            if let Fields::Named(fields) = &data.fields {
1361                for field in &fields.named {
1362                    check_field(field, &mut checks)?;
1363                }
1364            }
1365        }
1366        Data::Enum(data) => {
1367            for variant in &data.variants {
1368                match &variant.fields {
1369                    Fields::Named(fields) => {
1370                        for field in &fields.named {
1371                            check_field(field, &mut checks)?;
1372                        }
1373                    }
1374                    Fields::Unnamed(fields) => {
1375                        for field in &fields.unnamed {
1376                            check_field(field, &mut checks)?;
1377                        }
1378                    }
1379                    Fields::Unit => {}
1380                }
1381            }
1382        }
1383        Data::Union(_) => {}
1384    }
1385
1386    Ok(quote! { #(#checks)* })
1387}
1388
1389// ============================================================================
1390// Builder Generation
1391// ============================================================================
1392
1393fn generate_builder(input: &DeriveInput, builder_name: &Ident) -> syn::Result<TokenStream2> {
1394    let name = &input.ident;
1395
1396    // Generate the SuggestBuilder for this type
1397    let suggest_builder = generate_suggest_builder(input)?;
1398
1399    // Generate Option builders for any Option<T> fields
1400    let option_builders = collect_option_builders(input);
1401
1402    // Collect all fields for suggest/assume methods on the main builder
1403    let mut suggest_methods = Vec::new();
1404    let mut assume_methods = Vec::new();
1405
1406    generate_builder_methods_for_type(
1407        input,
1408        "", // root prefix
1409        &mut suggest_methods,
1410        &mut assume_methods,
1411    )?;
1412
1413    // Generate with_suggestions body
1414    let with_suggestions_body = generate_with_suggestions_body(input);
1415
1416    Ok(quote! {
1417        /// Builder for running surveys with suggestions and assumptions
1418        pub struct #builder_name {
1419            suggestions: std::collections::HashMap<String, elicitor::ResponseValue>,
1420            assumptions: std::collections::HashMap<String, elicitor::ResponseValue>,
1421        }
1422
1423        impl #builder_name {
1424            /// Create a new builder
1425            pub fn new() -> Self {
1426                Self {
1427                    suggestions: std::collections::HashMap::new(),
1428                    assumptions: std::collections::HashMap::new(),
1429                }
1430            }
1431
1432            /// Set suggestions from an existing instance (all fields become suggested defaults)
1433            pub fn with_suggestions(mut self, instance: &#name) -> Self {
1434                #with_suggestions_body
1435                self
1436            }
1437
1438            #(#suggest_methods)*
1439            #(#assume_methods)*
1440
1441            /// Run the survey with the given backend
1442            pub fn run<B: elicitor::SurveyBackend>(
1443                self,
1444                backend: B,
1445            ) -> Result<#name, anyhow::Error> {
1446                let mut definition = #name::survey();
1447
1448                // Apply suggestions and assumptions to questions
1449                self.apply_to_definition(&mut definition);
1450
1451                // Collect responses
1452                let responses = backend.collect(
1453                    &definition,
1454                    &|value, responses, path| #name::validate_field(value, responses, path),
1455                ).map_err(Into::into)?;
1456
1457                // Reconstruct the type
1458                Ok(#name::from_responses(&responses))
1459            }
1460
1461            fn apply_to_definition(&self, definition: &mut elicitor::SurveyDefinition) {
1462                for question in &mut definition.questions {
1463                    self.apply_to_question(question, "");
1464                }
1465            }
1466
1467            fn apply_to_question(&self, question: &mut elicitor::Question, parent_prefix: &str) {
1468                let path_str = if parent_prefix.is_empty() {
1469                    question.path().as_str().to_string()
1470                } else if question.path().as_str().is_empty() {
1471                    parent_prefix.to_string()
1472                } else {
1473                    format!("{}.{}", parent_prefix, question.path().as_str())
1474                };
1475
1476                // Handle is_none marker for Option fields (assumptions only)
1477                let none_key = format!("{}.is_none", path_str);
1478                if let Some(elicitor::ResponseValue::Bool(true)) = self.assumptions.get(&none_key) {
1479                    // For assumed None, skip this question entirely
1480                    question.set_assumption(elicitor::ResponseValue::Bool(false));
1481                    return;
1482                }
1483                if let Some(elicitor::ResponseValue::Bool(true)) = self.suggestions.get(&none_key) {
1484                    // For suggested None, set a suggestion marker (backend handles this)
1485                    question.set_suggestion(elicitor::ResponseValue::Bool(false));
1486                }
1487
1488                // Check for assumption first (takes priority)
1489                if let Some(value) = self.assumptions.get(&path_str) {
1490                    question.set_assumption(value.clone());
1491                    return;
1492                }
1493
1494                // Then check for suggestion
1495                if let Some(value) = self.suggestions.get(&path_str) {
1496                    question.set_suggestion(value.clone());
1497                }
1498
1499                // Recurse into nested questions
1500                match question.kind_mut() {
1501                    elicitor::QuestionKind::AllOf(all_of) => {
1502                        for q in all_of.questions_mut() {
1503                            self.apply_to_question(q, &path_str);
1504                        }
1505                    }
1506                    elicitor::QuestionKind::OneOf(one_of) => {
1507                        // Handle selected_variant for enums
1508                        let variant_key = format!("{}.selected_variant", path_str);
1509                        if let Some(elicitor::ResponseValue::ChosenVariant(idx)) = self.assumptions.get(&variant_key) {
1510                            one_of.default = Some(*idx);
1511                        } else if let Some(elicitor::ResponseValue::ChosenVariant(idx)) = self.suggestions.get(&variant_key) {
1512                            one_of.default = Some(*idx);
1513                        }
1514
1515                        // Recurse into variant fields
1516                        for variant in &mut one_of.variants {
1517                            match &mut variant.kind {
1518                                elicitor::QuestionKind::AllOf(all_of) => {
1519                                    for q in all_of.questions_mut() {
1520                                        self.apply_to_question(q, &path_str);
1521                                    }
1522                                }
1523                                elicitor::QuestionKind::Unit => {}
1524                                other => {
1525                                    // For newtype variants, create a temporary question wrapper
1526                                    let mut temp_q = elicitor::Question::new(
1527                                        elicitor::ResponsePath::new("0"),
1528                                        "",
1529                                        std::mem::replace(other, elicitor::QuestionKind::Unit),
1530                                    );
1531                                    self.apply_to_question(&mut temp_q, &path_str);
1532                                    *other = std::mem::replace(temp_q.kind_mut(), elicitor::QuestionKind::Unit);
1533                                }
1534                            }
1535                        }
1536                    }
1537                    elicitor::QuestionKind::AnyOf(any_of) => {
1538                        // Handle selected_variants for multi-select
1539                        let variants_key = format!("{}.selected_variants", path_str);
1540                        if let Some(elicitor::ResponseValue::ChosenVariants(indices)) = self.assumptions.get(&variants_key) {
1541                            any_of.defaults = indices.clone();
1542                        } else if let Some(elicitor::ResponseValue::ChosenVariants(indices)) = self.suggestions.get(&variants_key) {
1543                            any_of.defaults = indices.clone();
1544                        }
1545
1546                        for variant in &mut any_of.variants {
1547                            if let elicitor::QuestionKind::AllOf(all_of) = &mut variant.kind {
1548                                for q in all_of.questions_mut() {
1549                                    self.apply_to_question(q, &path_str);
1550                                }
1551                            }
1552                        }
1553                    }
1554                    _ => {}
1555                }
1556            }
1557        }
1558
1559        impl Default for #builder_name {
1560            fn default() -> Self {
1561                Self::new()
1562            }
1563        }
1564
1565        #suggest_builder
1566
1567        #(#option_builders)*
1568    })
1569}
1570
1571/// Generate the SuggestBuilder struct and impl for a type.
1572/// This builder is used within closures to suggest/assume nested fields.
1573fn generate_suggest_builder(input: &DeriveInput) -> syn::Result<TokenStream2> {
1574    let name = &input.ident;
1575    let suggest_builder_name = format_ident!("{}SuggestBuilder", name);
1576
1577    match &input.data {
1578        Data::Struct(data) => {
1579            generate_suggest_builder_for_struct(name, &suggest_builder_name, data)
1580        }
1581        Data::Enum(data) => generate_suggest_builder_for_enum(name, &suggest_builder_name, data),
1582        Data::Union(_) => Ok(quote! {}),
1583    }
1584}
1585
1586/// Generate SuggestBuilder for a struct type
1587fn generate_suggest_builder_for_struct(
1588    _name: &Ident,
1589    suggest_builder_name: &Ident,
1590    data: &syn::DataStruct,
1591) -> syn::Result<TokenStream2> {
1592    let mut field_methods = Vec::new();
1593
1594    if let Fields::Named(fields) = &data.fields {
1595        for field in &fields.named {
1596            let field_name = field.ident.as_ref().unwrap();
1597            let field_name_str = field_name.to_string();
1598            let ty = &field.ty;
1599
1600            let method = generate_suggest_builder_field_method(&field_name_str, ty)?;
1601            field_methods.push(method);
1602        }
1603    } else if let Fields::Unnamed(fields) = &data.fields {
1604        for (i, field) in fields.unnamed.iter().enumerate() {
1605            let field_name_str = i.to_string();
1606            let ty = &field.ty;
1607
1608            let method = generate_suggest_builder_field_method(&field_name_str, ty)?;
1609            field_methods.push(method);
1610        }
1611    }
1612
1613    Ok(quote! {
1614        /// Builder for suggesting/assuming values for nested fields
1615        pub struct #suggest_builder_name<'a> {
1616            map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1617            prefix: String,
1618        }
1619
1620        impl<'a> #suggest_builder_name<'a> {
1621            fn new(
1622                map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1623                prefix: String,
1624            ) -> Self {
1625                Self { map, prefix }
1626            }
1627
1628            fn path(&self, field: &str) -> String {
1629                if self.prefix.is_empty() {
1630                    field.to_string()
1631                } else {
1632                    format!("{}.{}", self.prefix, field)
1633                }
1634            }
1635
1636            #(#field_methods)*
1637        }
1638    })
1639}
1640
1641/// Generate SuggestBuilder for an enum type
1642fn generate_suggest_builder_for_enum(
1643    name: &Ident,
1644    suggest_builder_name: &Ident,
1645    data: &syn::DataEnum,
1646) -> syn::Result<TokenStream2> {
1647    let mut select_methods = Vec::new();
1648    let mut variant_methods = Vec::new();
1649    let mut variant_builders = Vec::new();
1650
1651    for (idx, variant) in data.variants.iter().enumerate() {
1652        let variant_name = &variant.ident;
1653        let variant_snake = to_snake_case(&variant_name.to_string());
1654        let select_method_name = format_ident!("suggest_{}", variant_snake);
1655
1656        // Generate suggest_<variant>() method to pre-select this variant
1657        select_methods.push(quote! {
1658            /// Pre-select this variant as the suggested default choice
1659            pub fn #select_method_name(self) -> Self {
1660                self.map.insert(
1661                    format!("{}.selected_variant", self.prefix),
1662                    elicitor::ResponseValue::ChosenVariant(#idx),
1663                );
1664                self
1665            }
1666        });
1667
1668        // Generate <variant>(closure) method for variants with fields
1669        match &variant.fields {
1670            Fields::Named(fields) if !fields.named.is_empty() => {
1671                let variant_builder_name = format_ident!("{}{}SuggestBuilder", name, variant_name);
1672                let method_name = format_ident!("{}", variant_snake);
1673
1674                variant_methods.push(quote! {
1675                    /// Suggest values for this variant's fields
1676                    pub fn #method_name<F>(self, f: F) -> Self
1677                    where
1678                        F: FnOnce(#variant_builder_name<'_>) -> #variant_builder_name<'_>,
1679                    {
1680                        let builder = #variant_builder_name::new(self.map, self.prefix.clone());
1681                        f(builder);
1682                        self
1683                    }
1684                });
1685
1686                // Generate the variant's field builder
1687                let mut field_methods = Vec::new();
1688                for field in &fields.named {
1689                    let field_name = field.ident.as_ref().unwrap();
1690                    let field_name_str = field_name.to_string();
1691                    let ty = &field.ty;
1692
1693                    let method = generate_suggest_builder_field_method(&field_name_str, ty)?;
1694                    field_methods.push(method);
1695                }
1696
1697                variant_builders.push(quote! {
1698                    /// Builder for suggesting values for variant fields
1699                    pub struct #variant_builder_name<'a> {
1700                        map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1701                        prefix: String,
1702                    }
1703
1704                    impl<'a> #variant_builder_name<'a> {
1705                        fn new(
1706                            map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1707                            prefix: String,
1708                        ) -> Self {
1709                            Self { map, prefix }
1710                        }
1711
1712                        fn path(&self, field: &str) -> String {
1713                            if self.prefix.is_empty() {
1714                                field.to_string()
1715                            } else {
1716                                format!("{}.{}", self.prefix, field)
1717                            }
1718                        }
1719
1720                        #(#field_methods)*
1721                    }
1722                });
1723            }
1724            Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
1725                // Newtype variant - use the inner type's builder directly for complex types
1726                let field = &fields.unnamed[0];
1727                let ty = &field.ty;
1728                let type_name = type_to_string(ty);
1729                let method_name = format_ident!("{}", variant_snake);
1730
1731                // Check if it's a primitive type
1732                let is_primitive = matches!(
1733                    type_name.as_str(),
1734                    "String"
1735                        | "bool"
1736                        | "i8"
1737                        | "i16"
1738                        | "i32"
1739                        | "i64"
1740                        | "isize"
1741                        | "u8"
1742                        | "u16"
1743                        | "u32"
1744                        | "u64"
1745                        | "usize"
1746                        | "f32"
1747                        | "f64"
1748                        | "PathBuf"
1749                );
1750
1751                if is_primitive {
1752                    // For primitives, generate a direct value method
1753                    let (param_type, conversion) = match type_name.as_str() {
1754                        "String" => (
1755                            quote! { impl Into<String> },
1756                            quote! { elicitor::ResponseValue::String(value.into()) },
1757                        ),
1758                        "bool" => (
1759                            quote! { bool },
1760                            quote! { elicitor::ResponseValue::Bool(value) },
1761                        ),
1762                        "i8" | "i16" | "i32" | "i64" | "isize" => (
1763                            quote! { #ty },
1764                            quote! { elicitor::ResponseValue::Int(value as i64) },
1765                        ),
1766                        "u8" | "u16" | "u32" | "u64" | "usize" => (
1767                            quote! { #ty },
1768                            quote! { elicitor::ResponseValue::Int(value as i64) },
1769                        ),
1770                        "f32" | "f64" => (
1771                            quote! { #ty },
1772                            quote! { elicitor::ResponseValue::Float(value as f64) },
1773                        ),
1774                        "PathBuf" => (
1775                            quote! { impl Into<std::path::PathBuf> },
1776                            quote! { elicitor::ResponseValue::String(value.into().to_string_lossy().into_owned()) },
1777                        ),
1778                        _ => unreachable!(),
1779                    };
1780
1781                    variant_methods.push(quote! {
1782                        /// Suggest a value for this newtype variant
1783                        pub fn #method_name(self, value: #param_type) -> Self {
1784                            self.map.insert(
1785                                format!("{}.0", self.prefix),
1786                                #conversion,
1787                            );
1788                            self
1789                        }
1790                    });
1791                } else {
1792                    // For complex types, use the inner type's builder directly
1793                    let inner_builder_name = format_ident!("{}SuggestBuilder", type_name);
1794
1795                    variant_methods.push(quote! {
1796                        /// Suggest values for this newtype variant's inner type
1797                        pub fn #method_name<F>(self, f: F) -> Self
1798                        where
1799                            F: FnOnce(#inner_builder_name<'_>) -> #inner_builder_name<'_>,
1800                        {
1801                            let builder = #inner_builder_name::new(
1802                                self.map,
1803                                format!("{}.0", self.prefix),
1804                            );
1805                            f(builder);
1806                            self
1807                        }
1808                    });
1809                }
1810            }
1811            Fields::Unnamed(fields) if fields.unnamed.len() > 1 => {
1812                // Multi-field tuple variant - create intermediate builder
1813                let variant_builder_name = format_ident!("{}{}SuggestBuilder", name, variant_name);
1814                let method_name = format_ident!("{}", variant_snake);
1815
1816                variant_methods.push(quote! {
1817                    /// Suggest values for this variant's fields
1818                    pub fn #method_name<F>(self, f: F) -> Self
1819                    where
1820                        F: FnOnce(#variant_builder_name<'_>) -> #variant_builder_name<'_>,
1821                    {
1822                        let builder = #variant_builder_name::new(self.map, self.prefix.clone());
1823                        f(builder);
1824                        self
1825                    }
1826                });
1827
1828                // Generate the variant's field builder for tuple variants
1829                let mut field_methods = Vec::new();
1830                for (i, field) in fields.unnamed.iter().enumerate() {
1831                    let field_name_str = i.to_string();
1832                    let ty = &field.ty;
1833
1834                    let method = generate_suggest_builder_field_method(&field_name_str, ty)?;
1835                    field_methods.push(method);
1836                }
1837
1838                variant_builders.push(quote! {
1839                    /// Builder for suggesting values for variant fields
1840                    pub struct #variant_builder_name<'a> {
1841                        map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1842                        prefix: String,
1843                    }
1844
1845                    impl<'a> #variant_builder_name<'a> {
1846                        fn new(
1847                            map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1848                            prefix: String,
1849                        ) -> Self {
1850                            Self { map, prefix }
1851                        }
1852
1853                        fn path(&self, field: &str) -> String {
1854                            if self.prefix.is_empty() {
1855                                field.to_string()
1856                            } else {
1857                                format!("{}.{}", self.prefix, field)
1858                            }
1859                        }
1860
1861                        #(#field_methods)*
1862                    }
1863                });
1864            }
1865            _ => {
1866                // Unit variants - no closure method needed
1867            }
1868        }
1869    }
1870
1871    Ok(quote! {
1872        /// Builder for suggesting/assuming values for enum variants
1873        pub struct #suggest_builder_name<'a> {
1874            map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1875            prefix: String,
1876        }
1877
1878        impl<'a> #suggest_builder_name<'a> {
1879            fn new(
1880                map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
1881                prefix: String,
1882            ) -> Self {
1883                Self { map, prefix }
1884            }
1885
1886            #(#select_methods)*
1887            #(#variant_methods)*
1888        }
1889
1890        #(#variant_builders)*
1891    })
1892}
1893
1894/// Generate a single field method for a SuggestBuilder
1895fn generate_suggest_builder_field_method(field_name: &str, ty: &Type) -> syn::Result<TokenStream2> {
1896    // For numeric field names (tuple structs), prefix with underscore
1897    let method_name = if field_name
1898        .chars()
1899        .next()
1900        .map(|c| c.is_ascii_digit())
1901        .unwrap_or(false)
1902    {
1903        format_ident!("_{}", field_name)
1904    } else {
1905        format_ident!("{}", field_name)
1906    };
1907    let type_name = type_to_string(ty);
1908
1909    // Check for Option<T>
1910    if let Some(inner_ty) = extract_option_inner_type(ty) {
1911        return generate_option_suggest_method(field_name, &inner_ty);
1912    }
1913
1914    // Skip Vec<T> types - they don't have a simple suggest pattern
1915    if extract_vec_inner_type(ty).is_some() {
1916        return Ok(quote! {});
1917    }
1918
1919    // Check for primitives
1920    let (param_type, conversion) = match type_name.as_str() {
1921        "String" => (
1922            Some(quote! { impl Into<String> }),
1923            Some(quote! { elicitor::ResponseValue::String(value.into()) }),
1924        ),
1925        "bool" => (
1926            Some(quote! { bool }),
1927            Some(quote! { elicitor::ResponseValue::Bool(value) }),
1928        ),
1929        "i8" | "i16" | "i32" | "i64" | "isize" => (
1930            Some(quote! { #ty }),
1931            Some(quote! { elicitor::ResponseValue::Int(value as i64) }),
1932        ),
1933        "u8" | "u16" | "u32" | "u64" | "usize" => (
1934            Some(quote! { #ty }),
1935            Some(quote! { elicitor::ResponseValue::Int(value as i64) }),
1936        ),
1937        "f32" | "f64" => (
1938            Some(quote! { #ty }),
1939            Some(quote! { elicitor::ResponseValue::Float(value as f64) }),
1940        ),
1941        "PathBuf" => (
1942            Some(quote! { impl Into<std::path::PathBuf> }),
1943            Some(
1944                quote! { elicitor::ResponseValue::String(value.into().to_string_lossy().into_owned()) },
1945            ),
1946        ),
1947        _ => (None, None), // Complex type - closure-based
1948    };
1949
1950    if let (Some(param_type), Some(conversion)) = (param_type, conversion) {
1951        // Primitive type - direct value method
1952        Ok(quote! {
1953            /// Suggest a value for this field
1954            pub fn #method_name(self, value: #param_type) -> Self {
1955                self.map.insert(self.path(#field_name), #conversion);
1956                self
1957            }
1958        })
1959    } else {
1960        // Complex type - closure-based method
1961        let inner_builder_name = format_ident!("{}SuggestBuilder", type_name);
1962
1963        Ok(quote! {
1964            /// Suggest values for this nested field
1965            pub fn #method_name<F>(self, f: F) -> Self
1966            where
1967                F: FnOnce(#inner_builder_name<'_>) -> #inner_builder_name<'_>,
1968            {
1969                let builder = #inner_builder_name::new(self.map, self.path(#field_name));
1970                f(builder);
1971                self
1972            }
1973        })
1974    }
1975}
1976
1977/// Generate suggest method for Option<T> fields within SuggestBuilder
1978fn generate_option_suggest_method(field_name: &str, inner_ty: &Type) -> syn::Result<TokenStream2> {
1979    let method_name = format_ident!("{}", field_name);
1980    let inner_type_name = type_to_string(inner_ty);
1981    let option_builder_name =
1982        format_ident!("Option{}SuggestBuilder", capitalize_first(&inner_type_name));
1983
1984    Ok(quote! {
1985        /// Suggest a value for this optional field
1986        pub fn #method_name<F>(self, f: F) -> Self
1987        where
1988            F: FnOnce(#option_builder_name<'_>) -> #option_builder_name<'_>,
1989        {
1990            let builder = #option_builder_name::new(self.map, self.path(#field_name));
1991            f(builder);
1992            self
1993        }
1994    })
1995}
1996
1997/// Generate an Option<T> SuggestBuilder type
1998fn generate_option_builder(inner_ty: &Type) -> TokenStream2 {
1999    let inner_type_name = type_to_string(inner_ty);
2000    let option_builder_name =
2001        format_ident!("Option{}SuggestBuilder", capitalize_first(&inner_type_name));
2002
2003    // Check if inner type is primitive
2004    let is_primitive = matches!(
2005        inner_type_name.as_str(),
2006        "String"
2007            | "bool"
2008            | "i8"
2009            | "i16"
2010            | "i32"
2011            | "i64"
2012            | "isize"
2013            | "u8"
2014            | "u16"
2015            | "u32"
2016            | "u64"
2017            | "usize"
2018            | "f32"
2019            | "f64"
2020            | "PathBuf"
2021    );
2022
2023    if is_primitive {
2024        // For primitive inner types, generate some(value) method
2025        let (some_param, some_conversion) = match inner_type_name.as_str() {
2026            "String" => (
2027                quote! { impl Into<String> },
2028                quote! { elicitor::ResponseValue::String(value.into()) },
2029            ),
2030            "bool" => (
2031                quote! { bool },
2032                quote! { elicitor::ResponseValue::Bool(value) },
2033            ),
2034            "i8" | "i16" | "i32" | "i64" | "isize" => (
2035                quote! { #inner_ty },
2036                quote! { elicitor::ResponseValue::Int(value as i64) },
2037            ),
2038            "u8" | "u16" | "u32" | "u64" | "usize" => (
2039                quote! { #inner_ty },
2040                quote! { elicitor::ResponseValue::Int(value as i64) },
2041            ),
2042            "f32" | "f64" => (
2043                quote! { #inner_ty },
2044                quote! { elicitor::ResponseValue::Float(value as f64) },
2045            ),
2046            "PathBuf" => (
2047                quote! { impl Into<std::path::PathBuf> },
2048                quote! { elicitor::ResponseValue::String(value.into().to_string_lossy().into_owned()) },
2049            ),
2050            _ => unreachable!(),
2051        };
2052
2053        quote! {
2054            /// Builder for suggesting Option<T> values
2055            pub struct #option_builder_name<'a> {
2056                map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
2057                prefix: String,
2058            }
2059
2060            impl<'a> #option_builder_name<'a> {
2061                fn new(
2062                    map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
2063                    prefix: String,
2064                ) -> Self {
2065                    Self { map, prefix }
2066                }
2067
2068                /// Suggest None (leave empty/skip this field)
2069                pub fn none(self) -> Self {
2070                    self.map.insert(
2071                        format!("{}.is_none", self.prefix),
2072                        elicitor::ResponseValue::Bool(true),
2073                    );
2074                    self
2075                }
2076
2077                /// Suggest Some with a value
2078                pub fn some(self, value: #some_param) -> Self {
2079                    self.map.insert(self.prefix.clone(), #some_conversion);
2080                    self
2081                }
2082            }
2083        }
2084    } else {
2085        // For complex inner types, generate some(closure) method
2086        let inner_builder_name = format_ident!("{}SuggestBuilder", inner_type_name);
2087
2088        quote! {
2089            /// Builder for suggesting Option<T> values
2090            pub struct #option_builder_name<'a> {
2091                map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
2092                prefix: String,
2093            }
2094
2095            impl<'a> #option_builder_name<'a> {
2096                fn new(
2097                    map: &'a mut std::collections::HashMap<String, elicitor::ResponseValue>,
2098                    prefix: String,
2099                ) -> Self {
2100                    Self { map, prefix }
2101                }
2102
2103                /// Suggest None (leave empty/skip this field)
2104                pub fn none(self) -> Self {
2105                    self.map.insert(
2106                        format!("{}.is_none", self.prefix),
2107                        elicitor::ResponseValue::Bool(true),
2108                    );
2109                    self
2110                }
2111
2112                /// Suggest Some with nested values
2113                pub fn some<F>(self, f: F) -> Self
2114                where
2115                    F: FnOnce(#inner_builder_name<'_>) -> #inner_builder_name<'_>,
2116                {
2117                    let builder = #inner_builder_name::new(self.map, self.prefix.clone());
2118                    f(builder);
2119                    self
2120                }
2121            }
2122        }
2123    }
2124}
2125
2126/// Collect all Option<T> types used in a struct/enum and generate their builders
2127fn collect_option_builders(input: &DeriveInput) -> Vec<TokenStream2> {
2128    let mut option_types: Vec<Type> = Vec::new();
2129    let mut seen_names: std::collections::HashSet<String> = std::collections::HashSet::new();
2130    let mut builders = Vec::new();
2131
2132    // Collect all Option types
2133    match &input.data {
2134        Data::Struct(data) => {
2135            collect_option_types_from_fields(&data.fields, &mut option_types, &mut seen_names);
2136        }
2137        Data::Enum(data) => {
2138            for variant in &data.variants {
2139                collect_option_types_from_fields(
2140                    &variant.fields,
2141                    &mut option_types,
2142                    &mut seen_names,
2143                );
2144            }
2145        }
2146        Data::Union(_) => {}
2147    }
2148
2149    // Generate builders for each unique Option type
2150    for ty in option_types {
2151        builders.push(generate_option_builder(&ty));
2152    }
2153
2154    builders
2155}
2156
2157fn collect_option_types_from_fields(
2158    fields: &Fields,
2159    option_types: &mut Vec<Type>,
2160    seen_names: &mut std::collections::HashSet<String>,
2161) {
2162    match fields {
2163        Fields::Named(fields) => {
2164            for field in &fields.named {
2165                if let Some(inner) = extract_option_inner_type(&field.ty) {
2166                    let name = type_to_string(&inner);
2167                    if seen_names.insert(name) {
2168                        option_types.push(inner);
2169                    }
2170                }
2171            }
2172        }
2173        Fields::Unnamed(fields) => {
2174            for field in &fields.unnamed {
2175                if let Some(inner) = extract_option_inner_type(&field.ty) {
2176                    let name = type_to_string(&inner);
2177                    if seen_names.insert(name) {
2178                        option_types.push(inner);
2179                    }
2180                }
2181            }
2182        }
2183        Fields::Unit => {}
2184    }
2185}
2186
2187/// Convert CamelCase to snake_case
2188fn to_snake_case(s: &str) -> String {
2189    let mut result = String::new();
2190    for (i, c) in s.chars().enumerate() {
2191        if c.is_uppercase() {
2192            if i > 0 {
2193                result.push('_');
2194            }
2195            result.push(c.to_lowercase().next().unwrap());
2196        } else {
2197            result.push(c);
2198        }
2199    }
2200    result
2201}
2202
2203/// Capitalize first letter
2204fn capitalize_first(s: &str) -> String {
2205    let mut chars = s.chars();
2206    match chars.next() {
2207        None => String::new(),
2208        Some(first) => first.to_uppercase().chain(chars).collect(),
2209    }
2210}
2211
2212fn generate_builder_methods_for_type(
2213    input: &DeriveInput,
2214    prefix: &str,
2215    suggest_methods: &mut Vec<TokenStream2>,
2216    assume_methods: &mut Vec<TokenStream2>,
2217) -> syn::Result<()> {
2218    match &input.data {
2219        Data::Struct(data) => {
2220            generate_builder_methods_for_fields(
2221                &data.fields,
2222                prefix,
2223                suggest_methods,
2224                assume_methods,
2225            )?;
2226        }
2227        Data::Enum(data) => {
2228            // For enums, generate methods for each variant's fields
2229            for variant in &data.variants {
2230                let variant_prefix = if prefix.is_empty() {
2231                    variant.ident.to_string().to_lowercase()
2232                } else {
2233                    format!("{}_{}", prefix, variant.ident.to_string().to_lowercase())
2234                };
2235                generate_builder_methods_for_fields(
2236                    &variant.fields,
2237                    &variant_prefix,
2238                    suggest_methods,
2239                    assume_methods,
2240                )?;
2241            }
2242        }
2243        Data::Union(_) => {}
2244    }
2245    Ok(())
2246}
2247
2248fn generate_builder_methods_for_fields(
2249    fields: &Fields,
2250    prefix: &str,
2251    suggest_methods: &mut Vec<TokenStream2>,
2252    assume_methods: &mut Vec<TokenStream2>,
2253) -> syn::Result<()> {
2254    match fields {
2255        Fields::Named(fields) => {
2256            for field in &fields.named {
2257                let field_name = field.ident.as_ref().unwrap();
2258                let field_name_str = field_name.to_string();
2259                let ty = &field.ty;
2260
2261                let method_suffix = if prefix.is_empty() {
2262                    field_name_str.clone()
2263                } else {
2264                    format!("{}_{}", prefix, field_name_str)
2265                };
2266
2267                let path_key = if prefix.is_empty() {
2268                    field_name_str.clone()
2269                } else {
2270                    format!("{}.{}", prefix.replace('_', "."), field_name_str)
2271                };
2272
2273                generate_suggest_assume_methods(
2274                    &method_suffix,
2275                    &path_key,
2276                    ty,
2277                    suggest_methods,
2278                    assume_methods,
2279                );
2280            }
2281        }
2282        Fields::Unnamed(fields) => {
2283            for (i, field) in fields.unnamed.iter().enumerate() {
2284                let field_name_str = i.to_string();
2285                let ty = &field.ty;
2286
2287                let method_suffix = if prefix.is_empty() {
2288                    field_name_str.clone()
2289                } else {
2290                    format!("{}_{}", prefix, field_name_str)
2291                };
2292
2293                let path_key = if prefix.is_empty() {
2294                    field_name_str
2295                } else {
2296                    format!("{}.{}", prefix.replace('_', "."), i)
2297                };
2298
2299                generate_suggest_assume_methods(
2300                    &method_suffix,
2301                    &path_key,
2302                    ty,
2303                    suggest_methods,
2304                    assume_methods,
2305                );
2306            }
2307        }
2308        Fields::Unit => {}
2309    }
2310    Ok(())
2311}
2312
2313fn generate_suggest_assume_methods(
2314    method_suffix: &str,
2315    path_key: &str,
2316    ty: &Type,
2317    suggest_methods: &mut Vec<TokenStream2>,
2318    assume_methods: &mut Vec<TokenStream2>,
2319) {
2320    let suggest_name = format_ident!("suggest_{}", method_suffix);
2321    let assume_name = format_ident!("assume_{}", method_suffix);
2322
2323    let type_name = type_to_string(ty);
2324
2325    // Skip Vec<T> types - they use multiselect and don't have a simple suggest pattern
2326    if extract_vec_inner_type(ty).is_some() {
2327        return;
2328    }
2329
2330    // Check for Option<T> - generate closure-based method
2331    if let Some(inner_ty) = extract_option_inner_type(ty) {
2332        let inner_type_name = type_to_string(&inner_ty);
2333        let option_builder_name =
2334            format_ident!("Option{}SuggestBuilder", capitalize_first(&inner_type_name));
2335
2336        suggest_methods.push(quote! {
2337            /// Suggest a value for this optional field (user can modify)
2338            pub fn #suggest_name<F>(mut self, f: F) -> Self
2339            where
2340                F: FnOnce(#option_builder_name<'_>) -> #option_builder_name<'_>,
2341            {
2342                let builder = #option_builder_name::new(&mut self.suggestions, #path_key.to_string());
2343                f(builder);
2344                self
2345            }
2346        });
2347
2348        assume_methods.push(quote! {
2349            /// Assume a value for this optional field (question is skipped)
2350            pub fn #assume_name<F>(mut self, f: F) -> Self
2351            where
2352                F: FnOnce(#option_builder_name<'_>) -> #option_builder_name<'_>,
2353            {
2354                let builder = #option_builder_name::new(&mut self.assumptions, #path_key.to_string());
2355                f(builder);
2356                self
2357            }
2358        });
2359        return;
2360    }
2361
2362    // Check for primitive types
2363    let (param_type, conversion) = match type_name.as_str() {
2364        "String" => (
2365            Some(quote! { impl Into<String> }),
2366            Some(quote! { elicitor::ResponseValue::String(value.into()) }),
2367        ),
2368        "bool" => (
2369            Some(quote! { bool }),
2370            Some(quote! { elicitor::ResponseValue::Bool(value) }),
2371        ),
2372        "i8" | "i16" | "i32" | "i64" | "isize" => (
2373            Some(quote! { #ty }),
2374            Some(quote! { elicitor::ResponseValue::Int(value as i64) }),
2375        ),
2376        "u8" | "u16" | "u32" | "u64" | "usize" => (
2377            Some(quote! { #ty }),
2378            Some(quote! { elicitor::ResponseValue::Int(value as i64) }),
2379        ),
2380        "f32" | "f64" => (
2381            Some(quote! { #ty }),
2382            Some(quote! { elicitor::ResponseValue::Float(value as f64) }),
2383        ),
2384        "PathBuf" => (
2385            Some(quote! { impl Into<std::path::PathBuf> }),
2386            Some(
2387                quote! { elicitor::ResponseValue::String(value.into().to_string_lossy().into_owned()) },
2388            ),
2389        ),
2390        _ => (None, None), // Complex type
2391    };
2392
2393    if let (Some(param_type), Some(conversion)) = (param_type, conversion) {
2394        // Primitive type - direct value methods
2395        suggest_methods.push(quote! {
2396            /// Suggest a default value for this field (user can modify)
2397            pub fn #suggest_name(mut self, value: #param_type) -> Self {
2398                self.suggestions.insert(#path_key.to_string(), #conversion);
2399                self
2400            }
2401        });
2402
2403        assume_methods.push(quote! {
2404            /// Assume a value for this field (question is skipped)
2405            pub fn #assume_name(mut self, value: #param_type) -> Self {
2406                self.assumptions.insert(#path_key.to_string(), #conversion);
2407                self
2408            }
2409        });
2410    } else {
2411        // Complex type - closure-based methods
2412        let inner_builder_name = format_ident!("{}SuggestBuilder", type_name);
2413
2414        suggest_methods.push(quote! {
2415            /// Suggest values for this nested field (user can modify)
2416            pub fn #suggest_name<F>(mut self, f: F) -> Self
2417            where
2418                F: FnOnce(#inner_builder_name<'_>) -> #inner_builder_name<'_>,
2419            {
2420                let builder = #inner_builder_name::new(&mut self.suggestions, #path_key.to_string());
2421                f(builder);
2422                self
2423            }
2424        });
2425
2426        assume_methods.push(quote! {
2427            /// Assume values for this nested field (questions are skipped)
2428            pub fn #assume_name<F>(mut self, f: F) -> Self
2429            where
2430                F: FnOnce(#inner_builder_name<'_>) -> #inner_builder_name<'_>,
2431            {
2432                let builder = #inner_builder_name::new(&mut self.assumptions, #path_key.to_string());
2433                f(builder);
2434                self
2435            }
2436        });
2437    }
2438}
2439
2440/// Generate the with_suggestions body for a struct
2441fn generate_with_suggestions_struct(data: &syn::DataStruct) -> TokenStream2 {
2442    match &data.fields {
2443        Fields::Named(fields) => {
2444            let insertions: Vec<_> = fields
2445                .named
2446                .iter()
2447                .filter_map(|f| {
2448                    let field_name = f.ident.as_ref()?;
2449                    let field_name_str = field_name.to_string();
2450                    let ty = &f.ty;
2451                    let type_name = type_to_string(ty);
2452
2453                    // Only handle primitive types directly
2454                    match type_name.as_str() {
2455                        "String" => Some(quote! {
2456                            self.suggestions.insert(
2457                                #field_name_str.to_string(),
2458                                elicitor::ResponseValue::String(instance.#field_name.clone())
2459                            );
2460                        }),
2461                        "bool" => Some(quote! {
2462                            self.suggestions.insert(
2463                                #field_name_str.to_string(),
2464                                elicitor::ResponseValue::Bool(instance.#field_name)
2465                            );
2466                        }),
2467                        "i8" | "i16" | "i32" | "i64" | "isize" => Some(quote! {
2468                            self.suggestions.insert(
2469                                #field_name_str.to_string(),
2470                                elicitor::ResponseValue::Int(instance.#field_name as i64)
2471                            );
2472                        }),
2473                        "u8" | "u16" | "u32" | "u64" | "usize" => Some(quote! {
2474                            self.suggestions.insert(
2475                                #field_name_str.to_string(),
2476                                elicitor::ResponseValue::Int(instance.#field_name as i64)
2477                            );
2478                        }),
2479                        "f32" | "f64" => Some(quote! {
2480                            self.suggestions.insert(
2481                                #field_name_str.to_string(),
2482                                elicitor::ResponseValue::Float(instance.#field_name as f64)
2483                            );
2484                        }),
2485                        "PathBuf" => Some(quote! {
2486                            self.suggestions.insert(
2487                                #field_name_str.to_string(),
2488                                elicitor::ResponseValue::String(instance.#field_name.display().to_string())
2489                            );
2490                        }),
2491                        _ => None, // Skip complex types
2492                    }
2493                })
2494                .collect();
2495
2496            quote! { #(#insertions)* }
2497        }
2498        Fields::Unnamed(fields) => {
2499            let insertions: Vec<_> = fields
2500                .unnamed
2501                .iter()
2502                .enumerate()
2503                .filter_map(|(i, f)| {
2504                    let idx = syn::Index::from(i);
2505                    let field_name_str = i.to_string();
2506                    let ty = &f.ty;
2507                    let type_name = type_to_string(ty);
2508
2509                    match type_name.as_str() {
2510                        "String" => Some(quote! {
2511                            self.suggestions.insert(
2512                                #field_name_str.to_string(),
2513                                elicitor::ResponseValue::String(instance.#idx.clone())
2514                            );
2515                        }),
2516                        "bool" => Some(quote! {
2517                            self.suggestions.insert(
2518                                #field_name_str.to_string(),
2519                                elicitor::ResponseValue::Bool(instance.#idx)
2520                            );
2521                        }),
2522                        "i8" | "i16" | "i32" | "i64" | "isize" => Some(quote! {
2523                            self.suggestions.insert(
2524                                #field_name_str.to_string(),
2525                                elicitor::ResponseValue::Int(instance.#idx as i64)
2526                            );
2527                        }),
2528                        "u8" | "u16" | "u32" | "u64" | "usize" => Some(quote! {
2529                            self.suggestions.insert(
2530                                #field_name_str.to_string(),
2531                                elicitor::ResponseValue::Int(instance.#idx as i64)
2532                            );
2533                        }),
2534                        "f32" | "f64" => Some(quote! {
2535                            self.suggestions.insert(
2536                                #field_name_str.to_string(),
2537                                elicitor::ResponseValue::Float(instance.#idx as f64)
2538                            );
2539                        }),
2540                        _ => None,
2541                    }
2542                })
2543                .collect();
2544
2545            quote! { #(#insertions)* }
2546        }
2547        Fields::Unit => quote! {},
2548    }
2549}
2550
2551/// Generate with_suggestions body based on the input type
2552fn generate_with_suggestions_body(input: &DeriveInput) -> TokenStream2 {
2553    match &input.data {
2554        Data::Struct(data) => generate_with_suggestions_struct(data),
2555        Data::Enum(_) => {
2556            // Enums are complex - skip for now
2557            quote! {}
2558        }
2559        Data::Union(_) => quote! {},
2560    }
2561}