facet_solver/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#![doc = include_str!("../README.md")]
3
4extern crate alloc;
5
6use alloc::borrow::Cow;
7use alloc::collections::BTreeMap;
8use alloc::collections::BTreeSet;
9use alloc::string::{String, ToString};
10use alloc::vec;
11use alloc::vec::Vec;
12use core::fmt;
13
14use facet_core::{Def, Field, Shape, StructType, Type, UserType, Variant};
15
16// Re-export resolution types from facet-reflect
17pub use facet_reflect::{
18    DuplicateFieldError, FieldInfo, FieldPath, KeyPath, MatchResult, PathSegment, Resolution,
19    VariantSelection,
20};
21
22/// Cached schema for a type that may contain flattened fields.
23///
24/// This is computed once per Shape and can be cached forever since
25/// type information is static.
26#[derive(Debug)]
27pub struct Schema {
28    /// The shape this schema is for (kept for future caching key)
29    #[allow(dead_code)]
30    shape: &'static Shape,
31
32    /// All possible resolutions of this type.
33    /// For types with no enums in flatten paths, this has exactly 1 entry.
34    /// For types with enums, this has one entry per valid combination of variants.
35    resolutions: Vec<Resolution>,
36
37    /// Inverted index: field_name → bitmask of configuration indices.
38    /// Bit i is set if `resolutions[i]` contains this field.
39    /// Uses a `Vec<u64>` to support arbitrary numbers of resolutions.
40    field_to_resolutions: BTreeMap<&'static str, ResolutionSet>,
41}
42
43/// Handle that identifies a specific resolution inside a schema.
44#[derive(Debug, Clone, Copy)]
45pub struct ResolutionHandle<'a> {
46    index: usize,
47    resolution: &'a Resolution,
48}
49
50impl<'a> PartialEq for ResolutionHandle<'a> {
51    fn eq(&self, other: &Self) -> bool {
52        self.index == other.index
53    }
54}
55
56impl<'a> Eq for ResolutionHandle<'a> {}
57
58impl<'a> ResolutionHandle<'a> {
59    /// Internal helper to build a handle for an index within a schema.
60    fn from_schema(schema: &'a Schema, index: usize) -> Self {
61        Self {
62            index,
63            resolution: &schema.resolutions[index],
64        }
65    }
66
67    /// Resolution index within the originating schema.
68    pub const fn index(self) -> usize {
69        self.index
70    }
71
72    /// Access the underlying resolution metadata.
73    pub const fn resolution(self) -> &'a Resolution {
74        self.resolution
75    }
76}
77
78/// A set of configuration indices, stored as a bitmask for O(1) intersection.
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub struct ResolutionSet {
81    /// Bitmask where bit i indicates `resolutions[i]` is in the set.
82    /// For most types, a single u64 suffices (up to 64 configs).
83    bits: Vec<u64>,
84    /// Number of resolutions in the set.
85    count: usize,
86}
87
88impl ResolutionSet {
89    /// Create an empty config set.
90    fn empty(num_resolutions: usize) -> Self {
91        let num_words = num_resolutions.div_ceil(64);
92        Self {
93            bits: vec![0; num_words],
94            count: 0,
95        }
96    }
97
98    /// Create a full config set (all configs present).
99    fn full(num_resolutions: usize) -> Self {
100        let num_words = num_resolutions.div_ceil(64);
101        let mut bits = vec![!0u64; num_words];
102        // Clear bits beyond num_resolutions
103        if !num_resolutions.is_multiple_of(64) {
104            let last_word_bits = num_resolutions % 64;
105            bits[num_words - 1] = (1u64 << last_word_bits) - 1;
106        }
107        Self {
108            bits,
109            count: num_resolutions,
110        }
111    }
112
113    /// Insert a configuration index.
114    fn insert(&mut self, idx: usize) {
115        let word = idx / 64;
116        let bit = idx % 64;
117        if self.bits[word] & (1u64 << bit) == 0 {
118            self.bits[word] |= 1u64 << bit;
119            self.count += 1;
120        }
121    }
122
123    /// Intersect with another config set in place.
124    fn intersect_with(&mut self, other: &ResolutionSet) {
125        self.count = 0;
126        for (a, b) in self.bits.iter_mut().zip(other.bits.iter()) {
127            *a &= *b;
128            self.count += a.count_ones() as usize;
129        }
130    }
131
132    /// Get the number of resolutions in the set.
133    fn len(&self) -> usize {
134        self.count
135    }
136
137    /// Check if empty.
138    fn is_empty(&self) -> bool {
139        self.count == 0
140    }
141
142    /// Get the first (lowest) configuration index in the set.
143    fn first(&self) -> Option<usize> {
144        for (word_idx, &word) in self.bits.iter().enumerate() {
145            if word != 0 {
146                return Some(word_idx * 64 + word.trailing_zeros() as usize);
147            }
148        }
149        None
150    }
151
152    /// Iterate over configuration indices in the set.
153    fn iter(&self) -> impl Iterator<Item = usize> + '_ {
154        self.bits.iter().enumerate().flat_map(|(word_idx, &word)| {
155            (0..64).filter_map(move |bit| {
156                if word & (1u64 << bit) != 0 {
157                    Some(word_idx * 64 + bit)
158                } else {
159                    None
160                }
161            })
162        })
163    }
164}
165
166/// Find fields that could disambiguate between resolutions.
167/// Returns fields that exist in some but not all resolutions.
168fn find_disambiguating_fields(configs: &[&Resolution]) -> Vec<String> {
169    if configs.len() < 2 {
170        return Vec::new();
171    }
172
173    // Collect all field names across all configs
174    let mut all_fields: BTreeSet<&str> = BTreeSet::new();
175    for config in configs {
176        for name in config.fields().keys() {
177            all_fields.insert(name);
178        }
179    }
180
181    // Find fields that are in some but not all configs
182    let mut disambiguating = Vec::new();
183    for field in all_fields {
184        let count = configs.iter().filter(|c| c.field(field).is_some()).count();
185        if count > 0 && count < configs.len() {
186            disambiguating.push(field.to_string());
187        }
188    }
189
190    disambiguating
191}
192
193/// Information about a missing required field for error reporting.
194#[derive(Debug, Clone)]
195pub struct MissingFieldInfo {
196    /// The serialized field name (as it appears in input)
197    pub name: &'static str,
198    /// Full path to the field (e.g., "backend.connection.port")
199    pub path: String,
200    /// The Rust type that defines this field
201    pub defined_in: String,
202}
203
204impl MissingFieldInfo {
205    /// Create from a FieldInfo
206    fn from_field_info(info: &FieldInfo) -> Self {
207        Self {
208            name: info.serialized_name,
209            path: info.path.to_string(),
210            defined_in: info.value_shape.type_identifier.to_string(),
211        }
212    }
213}
214
215/// Information about why a specific candidate (resolution) failed to match.
216#[derive(Debug, Clone)]
217pub struct CandidateFailure {
218    /// Human-readable description of the variant (e.g., "DatabaseBackend::Postgres")
219    pub variant_name: String,
220    /// Required fields that were not provided in the input
221    pub missing_fields: Vec<MissingFieldInfo>,
222    /// Fields in the input that don't exist in this candidate
223    pub unknown_fields: Vec<String>,
224    /// Number of unknown fields that have "did you mean?" suggestions for this candidate
225    /// Higher = more likely the user intended this variant
226    pub suggestion_matches: usize,
227}
228
229/// Suggestion for a field that might have been misspelled.
230#[derive(Debug, Clone)]
231pub struct FieldSuggestion {
232    /// The unknown field from input
233    pub unknown: String,
234    /// The suggested correct field name
235    pub suggestion: &'static str,
236    /// Similarity score (0.0 to 1.0, higher is more similar)
237    pub similarity: f64,
238}
239
240/// Errors that can occur when building a schema.
241#[derive(Debug, Clone)]
242pub enum SchemaError {
243    /// A field name appears from multiple sources (parent struct and flattened struct)
244    DuplicateField(DuplicateFieldError),
245}
246
247impl From<DuplicateFieldError> for SchemaError {
248    fn from(err: DuplicateFieldError) -> Self {
249        SchemaError::DuplicateField(err)
250    }
251}
252
253impl fmt::Display for SchemaError {
254    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255        match self {
256            SchemaError::DuplicateField(err) => {
257                write!(
258                    f,
259                    "Duplicate field name '{}' from different sources: {} vs {}. \
260                     This usually means a parent struct and a flattened struct both \
261                     define a field with the same name.",
262                    err.field_name, err.first_path, err.second_path
263                )
264            }
265        }
266    }
267}
268
269#[cfg(feature = "std")]
270impl std::error::Error for SchemaError {}
271
272/// Errors that can occur during flatten resolution.
273#[derive(Debug, Clone)]
274pub enum SolverError {
275    /// No configuration matches the input fields
276    NoMatch {
277        /// The input fields that were provided
278        input_fields: Vec<String>,
279        /// Missing required fields (from the closest matching config) - simple names for backwards compat
280        missing_required: Vec<&'static str>,
281        /// Missing required fields with full path information
282        missing_required_detailed: Vec<MissingFieldInfo>,
283        /// Unknown fields that don't belong to any config
284        unknown_fields: Vec<String>,
285        /// Description of the closest matching configuration
286        closest_resolution: Option<String>,
287        /// Why each candidate failed to match (detailed per-candidate info)
288        candidate_failures: Vec<CandidateFailure>,
289        /// "Did you mean?" suggestions for unknown fields
290        suggestions: Vec<FieldSuggestion>,
291    },
292    /// Multiple resolutions match the input fields
293    Ambiguous {
294        /// Descriptions of the matching resolutions
295        candidates: Vec<String>,
296        /// Fields that could disambiguate (unique to specific configs)
297        disambiguating_fields: Vec<String>,
298    },
299}
300
301impl fmt::Display for SolverError {
302    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303        match self {
304            SolverError::NoMatch {
305                input_fields,
306                missing_required: _,
307                missing_required_detailed,
308                unknown_fields,
309                closest_resolution,
310                candidate_failures,
311                suggestions,
312            } => {
313                write!(f, "No matching configuration for fields {input_fields:?}")?;
314
315                // Show per-candidate failure reasons if available
316                if !candidate_failures.is_empty() {
317                    write!(f, "\n\nNo variant matched:")?;
318                    for failure in candidate_failures {
319                        write!(f, "\n  - {}", failure.variant_name)?;
320                        if !failure.missing_fields.is_empty() {
321                            let names: Vec<_> =
322                                failure.missing_fields.iter().map(|m| m.name).collect();
323                            if names.len() == 1 {
324                                write!(f, ": missing field '{}'", names[0])?;
325                            } else {
326                                write!(f, ": missing fields {names:?}")?;
327                            }
328                        }
329                        if !failure.unknown_fields.is_empty() {
330                            if failure.missing_fields.is_empty() {
331                                write!(f, ":")?;
332                            } else {
333                                write!(f, ",")?;
334                            }
335                            write!(f, " unknown fields {:?}", failure.unknown_fields)?;
336                        }
337                    }
338                } else if let Some(config) = closest_resolution {
339                    // Fallback to closest match if no per-candidate info
340                    write!(f, " (closest match: {config})")?;
341                    if !missing_required_detailed.is_empty() {
342                        write!(f, "; missing required fields:")?;
343                        for info in missing_required_detailed {
344                            write!(f, " {} (at path: {})", info.name, info.path)?;
345                        }
346                    }
347                }
348
349                // Show unknown fields with suggestions
350                if !unknown_fields.is_empty() {
351                    write!(f, "\n\nUnknown fields: {unknown_fields:?}")?;
352                }
353                for suggestion in suggestions {
354                    write!(
355                        f,
356                        "\n  Did you mean '{}' instead of '{}'?",
357                        suggestion.suggestion, suggestion.unknown
358                    )?;
359                }
360
361                Ok(())
362            }
363            SolverError::Ambiguous {
364                candidates,
365                disambiguating_fields,
366            } => {
367                write!(f, "Ambiguous: multiple resolutions match: {candidates:?}")?;
368                if !disambiguating_fields.is_empty() {
369                    write!(
370                        f,
371                        "; try adding one of these fields to disambiguate: {disambiguating_fields:?}"
372                    )?;
373                }
374                Ok(())
375            }
376        }
377    }
378}
379
380#[cfg(feature = "std")]
381impl std::error::Error for SolverError {}
382
383/// Compute a specificity score for a shape. Lower score = more specific.
384///
385/// This is used to disambiguate when a value could satisfy multiple types.
386/// For example, the value `42` fits both `u8` and `u16`, but `u8` is more
387/// specific (lower score), so it should be preferred.
388/// Compute a specificity score for a shape.
389///
390/// Lower score = more specific type. Used for type-based disambiguation
391/// where we want to try more specific types first (e.g., u8 before u16).
392pub fn specificity_score(shape: &'static Shape) -> u64 {
393    // Use type_identifier to determine specificity
394    // Smaller integer types are more specific
395    match shape.type_identifier {
396        "u8" | "i8" => 8,
397        "u16" | "i16" => 16,
398        "u32" | "i32" | "f32" => 32,
399        "u64" | "i64" | "f64" => 64,
400        "u128" | "i128" => 128,
401        "usize" | "isize" => 64, // Treat as 64-bit
402        // Other types get a high score (less specific)
403        _ => 1000,
404    }
405}
406
407// ============================================================================
408// Solver (State Machine)
409// ============================================================================
410
411/// Result of reporting a key to the solver.
412#[derive(Debug)]
413pub enum KeyResult<'a> {
414    /// All candidates have the same type for this key.
415    /// The deserializer can parse the value directly.
416    Unambiguous {
417        /// The shape all candidates expect for this field
418        shape: &'static Shape,
419    },
420
421    /// Candidates have different types for this key - need disambiguation.
422    /// Deserializer should parse the value, determine which fields it can
423    /// satisfy, and call `satisfy()` with the viable fields.
424    ///
425    /// **Important**: When multiple fields can be satisfied by the value,
426    /// pick the one with the lowest score (most specific). Scores are assigned
427    /// by specificity, e.g., `u8` has a lower score than `u16`.
428    Ambiguous {
429        /// The unique fields across remaining candidates (deduplicated by shape),
430        /// paired with a specificity score. Lower score = more specific type.
431        /// Deserializer should check which of these the value can satisfy,
432        /// then pick the one with the lowest score.
433        fields: Vec<(&'a FieldInfo, u64)>,
434    },
435
436    /// This key disambiguated to exactly one configuration.
437    Solved(ResolutionHandle<'a>),
438
439    /// This key doesn't exist in any remaining candidate.
440    Unknown,
441}
442
443/// Result of reporting which fields the value can satisfy.
444#[derive(Debug)]
445pub enum SatisfyResult<'a> {
446    /// Continue - still multiple candidates, keep feeding keys.
447    Continue,
448
449    /// Solved to exactly one configuration.
450    Solved(ResolutionHandle<'a>),
451
452    /// No configuration can accept the value (no fields were satisfied).
453    NoMatch,
454}
455
456/// State machine solver for lazy value-based disambiguation.
457///
458/// This solver only requests value inspection when candidates disagree on type.
459/// For keys where all candidates expect the same type, the deserializer can
460/// skip detailed value analysis.
461///
462/// # Example
463///
464/// ```rust
465/// use facet::Facet;
466/// use facet_solver::{Schema, Solver, KeyResult, SatisfyResult};
467///
468/// #[derive(Facet)]
469/// #[repr(u8)]
470/// enum NumericValue {
471///     Small(u8),
472///     Large(u16),
473/// }
474///
475/// #[derive(Facet)]
476/// struct Container {
477///     #[facet(flatten)]
478///     value: NumericValue,
479/// }
480///
481/// let schema = Schema::build(Container::SHAPE).unwrap();
482/// let mut solver = Solver::new(&schema);
483///
484/// // The field "0" has different types (u8 vs u16) - solver needs disambiguation
485/// match solver.see_key("0") {
486///     KeyResult::Ambiguous { fields } => {
487///         // Deserializer sees value "1000", checks which fields can accept it
488///         // u8 can't hold 1000, u16 can - so only report the u16 field
489///         // Fields come with specificity scores - lower = more specific
490///         let satisfied: Vec<_> = fields.iter()
491///             .filter(|(f, _score)| {
492///                 // deserializer's logic: can this value parse as this field's type?
493///                 f.value_shape.type_identifier == "u16"
494///             })
495///             .map(|(f, _)| *f)
496///             .collect();
497///
498///         match solver.satisfy(&satisfied) {
499///             SatisfyResult::Solved(config) => {
500///                 assert!(config.resolution().describe().contains("Large"));
501///             }
502///             _ => panic!("expected solved"),
503///         }
504///     }
505///     _ => panic!("expected Ambiguous"),
506/// }
507/// ```
508#[derive(Debug)]
509pub struct Solver<'a> {
510    /// Reference to the schema for configuration lookup
511    schema: &'a Schema,
512    /// Bitmask of remaining candidate configuration indices
513    candidates: ResolutionSet,
514    /// Set of seen keys for required field checking.
515    /// Uses Cow to allow both borrowed keys (zero-copy) and owned keys (when needed).
516    seen_keys: BTreeSet<Cow<'a, str>>,
517}
518
519impl<'a> Solver<'a> {
520    /// Create a new solver from a schema.
521    pub fn new(schema: &'a Schema) -> Self {
522        Self {
523            schema,
524            candidates: ResolutionSet::full(schema.resolutions.len()),
525            seen_keys: BTreeSet::new(),
526        }
527    }
528
529    /// Report a key. Returns what to do next.
530    ///
531    /// - `Unambiguous`: All candidates agree on the type - parse directly
532    /// - `Ambiguous`: Types differ - check which fields the value can satisfy
533    /// - `Solved`: Disambiguated to one config
534    /// - `Unknown`: Key not found in any candidate
535    ///
536    /// Accepts both borrowed (`&str`) and owned (`String`) keys via `Cow`.
537    pub fn see_key(&mut self, key: impl Into<Cow<'a, str>>) -> KeyResult<'a> {
538        let key = key.into();
539        self.seen_keys.insert(key.clone());
540
541        // Key-based filtering
542        let resolutions_with_key = match self.schema.field_to_resolutions.get(key.as_ref()) {
543            Some(set) => set,
544            None => return KeyResult::Unknown,
545        };
546
547        self.candidates.intersect_with(resolutions_with_key);
548
549        if self.candidates.is_empty() {
550            return KeyResult::Unknown;
551        }
552
553        // Check if we've disambiguated to exactly one
554        if self.candidates.len() == 1 {
555            let idx = self.candidates.first().unwrap();
556            return KeyResult::Solved(self.handle(idx));
557        }
558
559        // Collect unique fields (by shape pointer) across remaining candidates
560        let mut unique_fields: Vec<&'a FieldInfo> = Vec::new();
561        for idx in self.candidates.iter() {
562            let config = &self.schema.resolutions[idx];
563            if let Some(info) = config.field(key.as_ref()) {
564                // Deduplicate by shape pointer
565                if !unique_fields
566                    .iter()
567                    .any(|f| core::ptr::eq(f.value_shape, info.value_shape))
568                {
569                    unique_fields.push(info);
570                }
571            }
572        }
573
574        if unique_fields.len() == 1 {
575            // All candidates have the same type - unambiguous
576            KeyResult::Unambiguous {
577                shape: unique_fields[0].value_shape,
578            }
579        } else {
580            // Different types - need disambiguation
581            // Attach specificity scores so caller can pick most specific when multiple match
582            let fields_with_scores: Vec<_> = unique_fields
583                .into_iter()
584                .map(|f| (f, specificity_score(f.value_shape)))
585                .collect();
586            KeyResult::Ambiguous {
587                fields: fields_with_scores,
588            }
589        }
590    }
591
592    /// Report which fields the value can satisfy after `Ambiguous` result.
593    ///
594    /// The deserializer should pass the subset of fields (from the `Ambiguous` result)
595    /// that the actual value can be parsed into.
596    pub fn satisfy(&mut self, satisfied_fields: &[&FieldInfo]) -> SatisfyResult<'a> {
597        let satisfied_shapes: Vec<_> = satisfied_fields.iter().map(|f| f.value_shape).collect();
598        self.satisfy_shapes(&satisfied_shapes)
599    }
600
601    /// Report which shapes the value can satisfy after `Ambiguous` result from `probe_key`.
602    ///
603    /// This is the shape-based version of `satisfy`, used when disambiguating
604    /// by nested field types. The deserializer should pass the shapes that
605    /// the actual value can be parsed into.
606    ///
607    /// # Example
608    ///
609    /// ```rust
610    /// use facet::Facet;
611    /// use facet_solver::{Schema, Solver, KeyResult, SatisfyResult};
612    ///
613    /// #[derive(Facet)]
614    /// struct SmallPayload { value: u8 }
615    ///
616    /// #[derive(Facet)]
617    /// struct LargePayload { value: u16 }
618    ///
619    /// #[derive(Facet)]
620    /// #[repr(u8)]
621    /// enum PayloadKind {
622    ///     Small { payload: SmallPayload },
623    ///     Large { payload: LargePayload },
624    /// }
625    ///
626    /// #[derive(Facet)]
627    /// struct Container {
628    ///     #[facet(flatten)]
629    ///     inner: PayloadKind,
630    /// }
631    ///
632    /// let schema = Schema::build(Container::SHAPE).unwrap();
633    /// let mut solver = Solver::new(&schema);
634    ///
635    /// // Report nested key
636    /// solver.probe_key(&[], "payload");
637    ///
638    /// // At payload.value, value is 1000 - doesn't fit u8
639    /// // Get shapes at this path
640    /// let shapes = solver.get_shapes_at_path(&["payload", "value"]);
641    /// // Filter to shapes that can hold 1000
642    /// let works: Vec<_> = shapes.iter()
643    ///     .filter(|s| s.type_identifier == "u16")
644    ///     .copied()
645    ///     .collect();
646    /// solver.satisfy_shapes(&works);
647    /// ```
648    pub fn satisfy_shapes(&mut self, satisfied_shapes: &[&'static Shape]) -> SatisfyResult<'a> {
649        if satisfied_shapes.is_empty() {
650            self.candidates = ResolutionSet::empty(self.schema.resolutions.len());
651            return SatisfyResult::NoMatch;
652        }
653
654        let mut new_candidates = ResolutionSet::empty(self.schema.resolutions.len());
655        for idx in self.candidates.iter() {
656            let config = &self.schema.resolutions[idx];
657            // Check if any of this config's fields match the satisfied shapes
658            for field in config.fields().values() {
659                if satisfied_shapes
660                    .iter()
661                    .any(|s| core::ptr::eq(*s, field.value_shape))
662                {
663                    new_candidates.insert(idx);
664                    break;
665                }
666            }
667        }
668        self.candidates = new_candidates;
669
670        match self.candidates.len() {
671            0 => SatisfyResult::NoMatch,
672            1 => {
673                let idx = self.candidates.first().unwrap();
674                SatisfyResult::Solved(self.handle(idx))
675            }
676            _ => SatisfyResult::Continue,
677        }
678    }
679
680    /// Get the shapes at a nested path across all remaining candidates.
681    ///
682    /// This is useful when you have an `Ambiguous` result from `probe_key`
683    /// and need to know what types are possible at that path.
684    pub fn get_shapes_at_path(&self, path: &[&str]) -> Vec<&'static Shape> {
685        let mut shapes: Vec<&'static Shape> = Vec::new();
686        for idx in self.candidates.iter() {
687            let config = &self.schema.resolutions[idx];
688            if let Some(shape) = self.get_shape_at_path(config, path)
689                && !shapes.iter().any(|s| core::ptr::eq(*s, shape))
690            {
691                shapes.push(shape);
692            }
693        }
694        shapes
695    }
696
697    /// Report which shapes at a nested path the value can satisfy.
698    ///
699    /// This is the path-aware version of `satisfy_shapes`, used when disambiguating
700    /// by nested field types after `probe_key`.
701    ///
702    /// - `path`: The full path to the field (e.g., `["payload", "value"]`)
703    /// - `satisfied_shapes`: The shapes that the value can be parsed into
704    pub fn satisfy_at_path(
705        &mut self,
706        path: &[&str],
707        satisfied_shapes: &[&'static Shape],
708    ) -> SatisfyResult<'a> {
709        if satisfied_shapes.is_empty() {
710            self.candidates = ResolutionSet::empty(self.schema.resolutions.len());
711            return SatisfyResult::NoMatch;
712        }
713
714        // Keep only candidates where the shape at this path is in the satisfied set
715        let mut new_candidates = ResolutionSet::empty(self.schema.resolutions.len());
716        for idx in self.candidates.iter() {
717            let config = &self.schema.resolutions[idx];
718            if let Some(shape) = self.get_shape_at_path(config, path)
719                && satisfied_shapes.iter().any(|s| core::ptr::eq(*s, shape))
720            {
721                new_candidates.insert(idx);
722            }
723        }
724        self.candidates = new_candidates;
725
726        match self.candidates.len() {
727            0 => SatisfyResult::NoMatch,
728            1 => {
729                let idx = self.candidates.first().unwrap();
730                SatisfyResult::Solved(self.handle(idx))
731            }
732            _ => SatisfyResult::Continue,
733        }
734    }
735
736    /// Get the current candidate resolutions.
737    pub fn candidates(&self) -> Vec<ResolutionHandle<'a>> {
738        self.candidates.iter().map(|idx| self.handle(idx)).collect()
739    }
740
741    /// Get the seen keys.
742    pub fn seen_keys(&self) -> &BTreeSet<Cow<'a, str>> {
743        &self.seen_keys
744    }
745
746    #[inline]
747    fn handle(&self, idx: usize) -> ResolutionHandle<'a> {
748        ResolutionHandle::from_schema(self.schema, idx)
749    }
750
751    /// Hint that a specific enum variant should be selected.
752    ///
753    /// This filters the candidates to only those resolutions where at least one
754    /// variant selection has the given variant name. This is useful for explicit
755    /// type disambiguation via annotations (e.g., KDL type annotations like `(Http)node`).
756    ///
757    /// Returns `true` if at least one candidate remains after filtering, `false` if
758    /// no candidates match the variant name (in which case candidates are unchanged).
759    ///
760    /// # Example
761    ///
762    /// ```rust
763    /// use facet::Facet;
764    /// use facet_solver::{Schema, Solver};
765    ///
766    /// #[derive(Facet)]
767    /// struct HttpSource { url: String }
768    ///
769    /// #[derive(Facet)]
770    /// struct GitSource { url: String, branch: String }
771    ///
772    /// #[derive(Facet)]
773    /// #[repr(u8)]
774    /// enum SourceKind {
775    ///     Http(HttpSource),
776    ///     Git(GitSource),
777    /// }
778    ///
779    /// #[derive(Facet)]
780    /// struct Source {
781    ///     #[facet(flatten)]
782    ///     kind: SourceKind,
783    /// }
784    ///
785    /// let schema = Schema::build(Source::SHAPE).unwrap();
786    /// let mut solver = Solver::new(&schema);
787    ///
788    /// // Without hint, both variants are candidates
789    /// assert_eq!(solver.candidates().len(), 2);
790    ///
791    /// // Hint at Http variant
792    /// assert!(solver.hint_variant("Http"));
793    /// assert_eq!(solver.candidates().len(), 1);
794    /// ```
795    pub fn hint_variant(&mut self, variant_name: &str) -> bool {
796        // Build a set of configs that have this variant name
797        let mut matching = ResolutionSet::empty(self.schema.resolutions.len());
798
799        for idx in self.candidates.iter() {
800            let config = &self.schema.resolutions[idx];
801            // Check if any variant selection matches the given name
802            if config
803                .variant_selections()
804                .iter()
805                .any(|vs| vs.variant_name == variant_name)
806            {
807                matching.insert(idx);
808            }
809        }
810
811        if matching.is_empty() {
812            // No matches - keep candidates unchanged
813            false
814        } else {
815            self.candidates = matching;
816            true
817        }
818    }
819
820    /// Mark a key as seen without filtering candidates.
821    ///
822    /// This is useful when the key is known to be present through means other than
823    /// parsing (e.g., type annotations). Call this after `hint_variant` to mark
824    /// the variant name as seen so that `finish()` doesn't report it as missing.
825    pub fn mark_seen(&mut self, key: impl Into<Cow<'a, str>>) {
826        self.seen_keys.insert(key.into());
827    }
828
829    /// Report a key at a nested path. Returns what to do next.
830    ///
831    /// This is the depth-aware version of `see_key`. Use this when probing
832    /// nested structures where disambiguation might require looking inside objects.
833    ///
834    /// - `path`: The ancestor keys (e.g., `["payload"]` when inside a payload object)
835    /// - `key`: The key found at this level (e.g., `"value"`)
836    ///
837    /// # Example
838    ///
839    /// ```rust
840    /// use facet::Facet;
841    /// use facet_solver::{Schema, Solver, KeyResult};
842    ///
843    /// #[derive(Facet)]
844    /// struct SmallPayload { value: u8 }
845    ///
846    /// #[derive(Facet)]
847    /// struct LargePayload { value: u16 }
848    ///
849    /// #[derive(Facet)]
850    /// #[repr(u8)]
851    /// enum PayloadKind {
852    ///     Small { payload: SmallPayload },
853    ///     Large { payload: LargePayload },
854    /// }
855    ///
856    /// #[derive(Facet)]
857    /// struct Container {
858    ///     #[facet(flatten)]
859    ///     inner: PayloadKind,
860    /// }
861    ///
862    /// let schema = Schema::build(Container::SHAPE).unwrap();
863    /// let mut solver = Solver::new(&schema);
864    ///
865    /// // "payload" exists in both - keep going
866    /// solver.probe_key(&[], "payload");
867    ///
868    /// // "value" inside payload - both have it but different types!
869    /// match solver.probe_key(&["payload"], "value") {
870    ///     KeyResult::Ambiguous { fields } => {
871    ///         // fields is Vec<(&FieldInfo, u64)> - field + specificity score
872    ///         // Deserializer checks: 1000 fits u16 but not u8
873    ///         // When multiple match, pick the one with lowest score (most specific)
874    ///     }
875    ///     _ => {}
876    /// }
877    /// ```
878    pub fn probe_key(&mut self, path: &[&str], key: &str) -> KeyResult<'a> {
879        // Build full path
880        let mut full_path: Vec<&str> = path.to_vec();
881        full_path.push(key);
882
883        // Filter candidates to only those that have this key path
884        let mut new_candidates = ResolutionSet::empty(self.schema.resolutions.len());
885        for idx in self.candidates.iter() {
886            let config = &self.schema.resolutions[idx];
887            if config.has_key_path(&full_path) {
888                new_candidates.insert(idx);
889            }
890        }
891        self.candidates = new_candidates;
892
893        if self.candidates.is_empty() {
894            return KeyResult::Unknown;
895        }
896
897        // Check if we've disambiguated to exactly one
898        if self.candidates.len() == 1 {
899            let idx = self.candidates.first().unwrap();
900            return KeyResult::Solved(self.handle(idx));
901        }
902
903        // Get the shape at this path for each remaining candidate
904        // We need to traverse the type tree to find the actual field type
905        let mut unique_shapes: Vec<(&'static Shape, usize)> = Vec::new(); // (shape, resolution_idx)
906
907        for idx in self.candidates.iter() {
908            let config = &self.schema.resolutions[idx];
909            if let Some(shape) = self.get_shape_at_path(config, &full_path) {
910                // Deduplicate by shape pointer
911                if !unique_shapes.iter().any(|(s, _)| core::ptr::eq(*s, shape)) {
912                    unique_shapes.push((shape, idx));
913                }
914            }
915        }
916
917        match unique_shapes.len() {
918            0 => KeyResult::Unknown,
919            1 => {
920                // All candidates have the same type at this path - unambiguous
921                KeyResult::Unambiguous {
922                    shape: unique_shapes[0].0,
923                }
924            }
925            _ => {
926                // Different types at this path - need disambiguation
927                // Build FieldInfo with scores for each unique shape
928                let fields: Vec<(&'a FieldInfo, u64)> = unique_shapes
929                    .iter()
930                    .filter_map(|(shape, idx)| {
931                        let config = &self.schema.resolutions[*idx];
932                        // For nested paths, we need the parent field
933                        // e.g., for ["payload", "value"], get the "payload" field
934                        let field = if path.is_empty() {
935                            config.field(key)
936                        } else {
937                            // Return the top-level field that contains this path
938                            config.field(path[0])
939                        }?;
940                        Some((field, specificity_score(shape)))
941                    })
942                    .collect();
943
944                KeyResult::Ambiguous { fields }
945            }
946        }
947    }
948
949    /// Get the shape at a nested path within a configuration.
950    fn get_shape_at_path(&self, config: &'a Resolution, path: &[&str]) -> Option<&'static Shape> {
951        if path.is_empty() {
952            return None;
953        }
954
955        // Start with the top-level field
956        let top_field = config.field(path[0])?;
957        let mut current_shape = top_field.value_shape;
958
959        // Navigate through nested structs
960        for &key in &path[1..] {
961            current_shape = self.get_field_shape(current_shape, key)?;
962        }
963
964        Some(current_shape)
965    }
966
967    /// Get the shape of a field within a struct shape.
968    fn get_field_shape(&self, shape: &'static Shape, field_name: &str) -> Option<&'static Shape> {
969        use facet_core::{StructType, Type, UserType};
970
971        match shape.ty {
972            Type::User(UserType::Struct(StructType { fields, .. })) => {
973                for field in fields {
974                    if field.name == field_name {
975                        return Some(field.shape());
976                    }
977                }
978                None
979            }
980            _ => None,
981        }
982    }
983
984    /// Finish solving. Call this after all keys have been processed.
985    ///
986    /// This method is necessary because key-based filtering alone cannot disambiguate
987    /// when one variant's required fields are a subset of another's.
988    ///
989    /// # Why not just use `see_key()` results?
990    ///
991    /// `see_key()` returns `Solved` when a key *excludes* candidates down to one.
992    /// But when the input is a valid subset of multiple variants, no key excludes
993    /// anything — you need `finish()` to check which candidates have all their
994    /// required fields satisfied.
995    ///
996    /// # Example
997    ///
998    /// ```rust,ignore
999    /// enum Source {
1000    ///     Http { url: String },                  // required: url
1001    ///     Git { url: String, branch: String },   // required: url, branch
1002    /// }
1003    /// ```
1004    ///
1005    /// | Input                  | `see_key` behavior                        | Resolution            |
1006    /// |------------------------|-------------------------------------------|-----------------------|
1007    /// | `{ "url", "branch" }`  | `branch` excludes `Http` → candidates = 1 | Early `Solved(Git)`   |
1008    /// | `{ "url" }`            | both have `url` → candidates = 2          | `finish()` → `Http`   |
1009    ///
1010    /// In the second case, no key ever excludes a candidate. Only `finish()` can
1011    /// determine that `Git` is missing its required `branch` field, leaving `Http`
1012    /// as the sole viable configuration.
1013    #[allow(clippy::result_large_err)] // SolverError intentionally contains detailed diagnostic info
1014    pub fn finish(self) -> Result<ResolutionHandle<'a>, SolverError> {
1015        let Solver {
1016            schema,
1017            candidates,
1018            seen_keys,
1019        } = self;
1020
1021        // Compute all known fields across all resolutions (for unknown field detection)
1022        let all_known_fields: BTreeSet<&'static str> = schema
1023            .resolutions
1024            .iter()
1025            .flat_map(|r| r.fields().keys().copied())
1026            .collect();
1027
1028        // Find unknown fields (fields in input that don't exist in ANY resolution)
1029        let unknown_fields: Vec<String> = seen_keys
1030            .iter()
1031            .filter(|k| !all_known_fields.contains(k.as_ref()))
1032            .map(|s| s.to_string())
1033            .collect();
1034
1035        // Compute suggestions for unknown fields
1036        let suggestions = compute_suggestions(&unknown_fields, &all_known_fields);
1037
1038        if candidates.is_empty() {
1039            // Build per-candidate failure info for all resolutions
1040            let mut candidate_failures: Vec<CandidateFailure> = schema
1041                .resolutions
1042                .iter()
1043                .map(|config| build_candidate_failure(config, &seen_keys))
1044                .collect();
1045
1046            // Sort by closeness (best match first)
1047            sort_candidates_by_closeness(&mut candidate_failures);
1048
1049            return Err(SolverError::NoMatch {
1050                input_fields: seen_keys.iter().map(|s| s.to_string()).collect(),
1051                missing_required: Vec::new(),
1052                missing_required_detailed: Vec::new(),
1053                unknown_fields,
1054                closest_resolution: None,
1055                candidate_failures,
1056                suggestions,
1057            });
1058        }
1059
1060        // Filter candidates to only those that have all required fields satisfied
1061        let viable: Vec<usize> = candidates
1062            .iter()
1063            .filter(|idx| {
1064                let config = &schema.resolutions[*idx];
1065                config
1066                    .required_field_names()
1067                    .iter()
1068                    .all(|f| seen_keys.iter().any(|k| k.as_ref() == *f))
1069            })
1070            .collect();
1071
1072        match viable.len() {
1073            0 => {
1074                // No viable candidates - build per-candidate failure info
1075                let mut candidate_failures: Vec<CandidateFailure> = candidates
1076                    .iter()
1077                    .map(|idx| {
1078                        let config = &schema.resolutions[idx];
1079                        build_candidate_failure(config, &seen_keys)
1080                    })
1081                    .collect();
1082
1083                // Sort by closeness (best match first)
1084                sort_candidates_by_closeness(&mut candidate_failures);
1085
1086                // For backwards compatibility, also populate the "closest" fields
1087                // Now use the first (closest) candidate after sorting
1088                let closest_name = candidate_failures.first().map(|f| f.variant_name.clone());
1089                let closest_config = closest_name
1090                    .as_ref()
1091                    .and_then(|name| schema.resolutions.iter().find(|r| r.describe() == *name));
1092
1093                let (missing, missing_detailed, closest_resolution) =
1094                    if let Some(config) = closest_config {
1095                        let missing: Vec<_> = config
1096                            .required_field_names()
1097                            .iter()
1098                            .filter(|f| !seen_keys.iter().any(|k| k.as_ref() == **f))
1099                            .copied()
1100                            .collect();
1101                        let missing_detailed: Vec<_> = missing
1102                            .iter()
1103                            .filter_map(|name| config.field(name))
1104                            .map(MissingFieldInfo::from_field_info)
1105                            .collect();
1106                        (missing, missing_detailed, Some(config.describe()))
1107                    } else {
1108                        (Vec::new(), Vec::new(), None)
1109                    };
1110
1111                Err(SolverError::NoMatch {
1112                    input_fields: seen_keys.iter().map(|s| s.to_string()).collect(),
1113                    missing_required: missing,
1114                    missing_required_detailed: missing_detailed,
1115                    unknown_fields,
1116                    closest_resolution,
1117                    candidate_failures,
1118                    suggestions,
1119                })
1120            }
1121            1 => {
1122                // Exactly one viable candidate - success!
1123                Ok(ResolutionHandle::from_schema(schema, viable[0]))
1124            }
1125            _ => {
1126                // Multiple viable candidates - ambiguous!
1127                let configs: Vec<_> = viable.iter().map(|&idx| &schema.resolutions[idx]).collect();
1128                let candidates: Vec<String> = configs.iter().map(|c| c.describe()).collect();
1129                let disambiguating_fields = find_disambiguating_fields(&configs);
1130
1131                Err(SolverError::Ambiguous {
1132                    candidates,
1133                    disambiguating_fields,
1134                })
1135            }
1136        }
1137    }
1138}
1139
1140/// Build a CandidateFailure for a resolution given the seen keys.
1141fn build_candidate_failure<'a>(
1142    config: &Resolution,
1143    seen_keys: &BTreeSet<Cow<'a, str>>,
1144) -> CandidateFailure {
1145    let missing_fields: Vec<MissingFieldInfo> = config
1146        .required_field_names()
1147        .iter()
1148        .filter(|f| !seen_keys.iter().any(|k| k.as_ref() == **f))
1149        .filter_map(|f| config.field(f))
1150        .map(MissingFieldInfo::from_field_info)
1151        .collect();
1152
1153    let unknown_fields: Vec<String> = seen_keys
1154        .iter()
1155        .filter(|k| !config.fields().contains_key(k.as_ref()))
1156        .map(|s| s.to_string())
1157        .collect();
1158
1159    // Compute closeness score for ranking
1160    let suggestion_matches = compute_closeness_score(&unknown_fields, &missing_fields, config);
1161
1162    CandidateFailure {
1163        variant_name: config.describe(),
1164        missing_fields,
1165        unknown_fields,
1166        suggestion_matches,
1167    }
1168}
1169
1170/// Compute a closeness score for ranking candidates.
1171/// Higher score = more likely the user intended this variant.
1172///
1173/// The score considers:
1174/// - Typo matches: unknown fields that are similar to known fields (weighted by similarity)
1175/// - Field coverage: if we fixed typos, would we have all required fields?
1176/// - Missing fields: fewer missing = better
1177/// - Unknown fields: fewer truly unknown (no suggestion) = better
1178#[cfg(feature = "suggestions")]
1179fn compute_closeness_score(
1180    unknown_fields: &[String],
1181    missing_fields: &[MissingFieldInfo],
1182    config: &Resolution,
1183) -> usize {
1184    const SIMILARITY_THRESHOLD: f64 = 0.6;
1185
1186    // Score components (scaled to integers for easy comparison)
1187    let mut typo_score: usize = 0;
1188    let mut fields_that_would_match: usize = 0;
1189
1190    // For each unknown field, find best matching known field
1191    for unknown in unknown_fields {
1192        let mut best_similarity = 0.0f64;
1193        let mut best_match: Option<&str> = None;
1194
1195        for known in config.fields().keys() {
1196            let similarity = strsim::jaro_winkler(unknown, known);
1197            if similarity >= SIMILARITY_THRESHOLD && similarity > best_similarity {
1198                best_similarity = similarity;
1199                best_match = Some(known);
1200            }
1201        }
1202
1203        if let Some(_matched_field) = best_match {
1204            // Weight by similarity: 0.6 -> 60 points, 1.0 -> 100 points
1205            typo_score += (best_similarity * 100.0) as usize;
1206            fields_that_would_match += 1;
1207        }
1208    }
1209
1210    // Calculate how many required fields would be satisfied if typos were fixed
1211    let required_count = config.required_field_names().len();
1212    let currently_missing = missing_fields.len();
1213    let would_be_missing = currently_missing.saturating_sub(fields_that_would_match);
1214
1215    // Coverage score: percentage of required fields that would be present
1216    let coverage_score = if required_count > 0 {
1217        ((required_count - would_be_missing) * 100) / required_count
1218    } else {
1219        100 // No required fields = perfect coverage
1220    };
1221
1222    // Penalty for truly unknown fields (no typo suggestion)
1223    let truly_unknown = unknown_fields.len().saturating_sub(fields_that_would_match);
1224    let unknown_penalty = truly_unknown * 10;
1225
1226    // Combine scores: typo matches are most important, then coverage, then penalties
1227    // Each typo match can give up to 100 points, so scale coverage to match
1228    typo_score + coverage_score.saturating_sub(unknown_penalty)
1229}
1230
1231/// Compute closeness score (no-op without suggestions feature).
1232#[cfg(not(feature = "suggestions"))]
1233fn compute_closeness_score(
1234    _unknown_fields: &[String],
1235    _missing_fields: &[MissingFieldInfo],
1236    _config: &Resolution,
1237) -> usize {
1238    0
1239}
1240
1241/// Sort candidate failures by closeness (best match first).
1242fn sort_candidates_by_closeness(failures: &mut [CandidateFailure]) {
1243    failures.sort_by(|a, b| {
1244        // Higher suggestion_matches (closeness score) first
1245        b.suggestion_matches.cmp(&a.suggestion_matches)
1246    });
1247}
1248
1249/// Compute "did you mean?" suggestions for unknown fields.
1250#[cfg(feature = "suggestions")]
1251fn compute_suggestions(
1252    unknown_fields: &[String],
1253    all_known_fields: &BTreeSet<&'static str>,
1254) -> Vec<FieldSuggestion> {
1255    const SIMILARITY_THRESHOLD: f64 = 0.6;
1256
1257    let mut suggestions = Vec::new();
1258
1259    for unknown in unknown_fields {
1260        let mut best_match: Option<(&'static str, f64)> = None;
1261
1262        for known in all_known_fields {
1263            let similarity = strsim::jaro_winkler(unknown, known);
1264            if similarity >= SIMILARITY_THRESHOLD
1265                && best_match.is_none_or(|(_, best_sim)| similarity > best_sim)
1266            {
1267                best_match = Some((known, similarity));
1268            }
1269        }
1270
1271        if let Some((suggestion, similarity)) = best_match {
1272            suggestions.push(FieldSuggestion {
1273                unknown: unknown.clone(),
1274                suggestion,
1275                similarity,
1276            });
1277        }
1278    }
1279
1280    suggestions
1281}
1282
1283/// Compute "did you mean?" suggestions for unknown fields (no-op without strsim).
1284#[cfg(not(feature = "suggestions"))]
1285fn compute_suggestions(
1286    _unknown_fields: &[String],
1287    _all_known_fields: &BTreeSet<&'static str>,
1288) -> Vec<FieldSuggestion> {
1289    Vec::new()
1290}
1291
1292// ============================================================================
1293// Probing Solver (Depth-Aware)
1294// ============================================================================
1295
1296/// Result of reporting a key to the probing solver.
1297#[derive(Debug)]
1298pub enum ProbeResult<'a> {
1299    /// Keep reporting keys - not yet disambiguated
1300    KeepGoing,
1301    /// Solved! Use this configuration
1302    Solved(&'a Resolution),
1303    /// No configuration matches the observed keys
1304    NoMatch,
1305}
1306
1307/// Depth-aware probing solver for streaming deserialization.
1308///
1309/// Unlike the batch solver, this solver accepts
1310/// key reports at arbitrary depths. It's designed for the "peek" strategy:
1311///
1312/// 1. Deserializer scans keys (without parsing values) and reports them
1313/// 2. Solver filters candidates based on which configs have that key path
1314/// 3. Once one candidate remains, solver returns `Solved`
1315/// 4. Deserializer rewinds and parses into the resolved type
1316///
1317/// # Example
1318///
1319/// ```rust
1320/// use facet::Facet;
1321/// use facet_solver::{Schema, ProbingSolver, ProbeResult};
1322///
1323/// #[derive(Facet)]
1324/// struct TextPayload { content: String }
1325///
1326/// #[derive(Facet)]
1327/// struct BinaryPayload { bytes: Vec<u8> }
1328///
1329/// #[derive(Facet)]
1330/// #[repr(u8)]
1331/// enum MessageKind {
1332///     Text { payload: TextPayload },
1333///     Binary { payload: BinaryPayload },
1334/// }
1335///
1336/// #[derive(Facet)]
1337/// struct Message {
1338///     id: String,
1339///     #[facet(flatten)]
1340///     kind: MessageKind,
1341/// }
1342///
1343/// let schema = Schema::build(Message::SHAPE).unwrap();
1344/// let mut solver = ProbingSolver::new(&schema);
1345///
1346/// // "id" exists in both configs - keep going
1347/// assert!(matches!(solver.probe_key(&[], "id"), ProbeResult::KeepGoing));
1348///
1349/// // "payload" exists in both configs - keep going
1350/// assert!(matches!(solver.probe_key(&[], "payload"), ProbeResult::KeepGoing));
1351///
1352/// // "content" inside payload only exists in Text - solved!
1353/// match solver.probe_key(&["payload"], "content") {
1354///     ProbeResult::Solved(config) => {
1355///         assert!(config.has_key_path(&["payload", "content"]));
1356///     }
1357///     _ => panic!("expected Solved"),
1358/// }
1359/// ```
1360#[derive(Debug)]
1361pub struct ProbingSolver<'a> {
1362    /// Remaining candidate resolutions
1363    candidates: Vec<&'a Resolution>,
1364}
1365
1366impl<'a> ProbingSolver<'a> {
1367    /// Create a new probing solver from a schema.
1368    pub fn new(schema: &'a Schema) -> Self {
1369        Self {
1370            candidates: schema.resolutions.iter().collect(),
1371        }
1372    }
1373
1374    /// Create a new probing solver from resolutions directly.
1375    pub fn from_resolutions(configs: &'a [Resolution]) -> Self {
1376        Self {
1377            candidates: configs.iter().collect(),
1378        }
1379    }
1380
1381    /// Report a key found at a path during probing.
1382    ///
1383    /// - `path`: The ancestor keys (e.g., `["payload"]` when inside the payload object)
1384    /// - `key`: The key found at this level (e.g., `"content"`)
1385    ///
1386    /// Returns what to do next.
1387    pub fn probe_key(&mut self, path: &[&str], key: &str) -> ProbeResult<'a> {
1388        // Build the full key path (runtime strings, compared against static schema)
1389        let mut full_path: Vec<&str> = path.to_vec();
1390        full_path.push(key);
1391
1392        // Filter to candidates that have this key path
1393        self.candidates.retain(|c| c.has_key_path(&full_path));
1394
1395        match self.candidates.len() {
1396            0 => ProbeResult::NoMatch,
1397            1 => ProbeResult::Solved(self.candidates[0]),
1398            _ => ProbeResult::KeepGoing,
1399        }
1400    }
1401
1402    /// Get the current candidate resolutions.
1403    pub fn candidates(&self) -> &[&'a Resolution] {
1404        &self.candidates
1405    }
1406
1407    /// Finish probing - returns Solved if exactly one candidate remains.
1408    pub fn finish(&self) -> ProbeResult<'a> {
1409        match self.candidates.len() {
1410            0 => ProbeResult::NoMatch,
1411            1 => ProbeResult::Solved(self.candidates[0]),
1412            _ => ProbeResult::KeepGoing, // Still ambiguous
1413        }
1414    }
1415}
1416
1417// ============================================================================
1418// Variant Format Classification
1419// ============================================================================
1420
1421/// Classification of an enum variant's expected serialized format.
1422///
1423/// This is used by deserializers to determine how to parse untagged enum variants
1424/// based on the YAML/JSON/etc. value type they encounter.
1425#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1426pub enum VariantFormat {
1427    /// Unit variant: no fields, serializes as the variant name or nothing for untagged
1428    Unit,
1429
1430    /// Newtype variant wrapping a scalar type (String, numbers, bool, etc.)
1431    /// Serializes as just the scalar value for untagged enums.
1432    NewtypeScalar {
1433        /// The shape of the inner scalar type
1434        inner_shape: &'static Shape,
1435    },
1436
1437    /// Newtype variant wrapping a struct
1438    /// Serializes as a mapping for untagged enums.
1439    NewtypeStruct {
1440        /// The shape of the inner struct type
1441        inner_shape: &'static Shape,
1442    },
1443
1444    /// Newtype variant wrapping a tuple struct/tuple
1445    /// Serializes as a sequence for untagged enums.
1446    NewtypeTuple {
1447        /// The shape of the inner tuple type
1448        inner_shape: &'static Shape,
1449        /// Number of elements in the inner tuple
1450        arity: usize,
1451    },
1452
1453    /// Newtype variant wrapping a sequence type (Vec, Array, Slice, Set)
1454    /// Serializes as a sequence for untagged enums.
1455    NewtypeSequence {
1456        /// The shape of the inner sequence type
1457        inner_shape: &'static Shape,
1458    },
1459
1460    /// Newtype variant wrapping another type (enum, map, etc.)
1461    NewtypeOther {
1462        /// The shape of the inner type
1463        inner_shape: &'static Shape,
1464    },
1465
1466    /// Tuple variant with multiple fields
1467    /// Serializes as a sequence for untagged enums.
1468    Tuple {
1469        /// Number of fields in the tuple
1470        arity: usize,
1471    },
1472
1473    /// Struct variant with named fields
1474    /// Serializes as a mapping for untagged enums.
1475    Struct,
1476}
1477
1478impl VariantFormat {
1479    /// Classify a variant's expected serialized format.
1480    pub fn from_variant(variant: &'static Variant) -> Self {
1481        use facet_core::StructKind;
1482
1483        let fields = variant.data.fields;
1484        let kind = variant.data.kind;
1485
1486        match kind {
1487            StructKind::Unit => VariantFormat::Unit,
1488            // TupleStruct and Tuple are both used for tuple-like variants
1489            // depending on how they're defined. Handle them the same way.
1490            StructKind::TupleStruct | StructKind::Tuple => {
1491                if fields.len() == 1 {
1492                    // Newtype variant - classify by inner type
1493                    let field_shape = fields[0].shape();
1494                    // Dereference through pointers to get the actual inner type
1495                    let inner_shape = deref_pointer(field_shape);
1496
1497                    // Check if this is a Spanned<T> wrapper and unwrap it for classification
1498                    // This allows untagged enum variants containing Spanned<String> etc.
1499                    // to match scalar values transparently
1500                    let classification_shape = if let Some(spanned_inner) =
1501                        facet_reflect::get_spanned_inner_shape(field_shape)
1502                    {
1503                        spanned_inner
1504                    } else {
1505                        field_shape
1506                    };
1507
1508                    if is_scalar_shape(classification_shape)
1509                        || is_unit_enum_shape(classification_shape)
1510                    {
1511                        // Scalars and unit-only enums both serialize as primitive values
1512                        // Store the classification shape (unwrapped from Spanned if needed)
1513                        // so that type matching works correctly for multi-variant untagged enums
1514                        VariantFormat::NewtypeScalar {
1515                            inner_shape: classification_shape,
1516                        }
1517                    } else if let Some(arity) = tuple_struct_arity(classification_shape) {
1518                        VariantFormat::NewtypeTuple { inner_shape, arity }
1519                    } else if is_named_struct_shape(classification_shape) {
1520                        VariantFormat::NewtypeStruct { inner_shape }
1521                    } else if is_sequence_shape(classification_shape) {
1522                        VariantFormat::NewtypeSequence { inner_shape }
1523                    } else {
1524                        VariantFormat::NewtypeOther { inner_shape }
1525                    }
1526                } else {
1527                    // Multi-field tuple variant
1528                    VariantFormat::Tuple {
1529                        arity: fields.len(),
1530                    }
1531                }
1532            }
1533            StructKind::Struct => VariantFormat::Struct,
1534        }
1535    }
1536
1537    /// Returns true if this variant expects a scalar value in untagged format.
1538    pub fn expects_scalar(&self) -> bool {
1539        matches!(self, VariantFormat::NewtypeScalar { .. })
1540    }
1541
1542    /// Returns true if this variant expects a sequence in untagged format.
1543    pub fn expects_sequence(&self) -> bool {
1544        matches!(
1545            self,
1546            VariantFormat::Tuple { .. }
1547                | VariantFormat::NewtypeTuple { .. }
1548                | VariantFormat::NewtypeSequence { .. }
1549        )
1550    }
1551
1552    /// Returns true if this variant expects a mapping in untagged format.
1553    pub fn expects_mapping(&self) -> bool {
1554        matches!(
1555            self,
1556            VariantFormat::Struct | VariantFormat::NewtypeStruct { .. }
1557        )
1558    }
1559
1560    /// Returns true if this is a unit variant (no data).
1561    pub fn is_unit(&self) -> bool {
1562        matches!(self, VariantFormat::Unit)
1563    }
1564}
1565
1566/// Dereference through pointer types (like `Box<T>`) to get the pointee shape.
1567/// Returns the original shape if it's not a pointer.
1568fn deref_pointer(shape: &'static Shape) -> &'static Shape {
1569    use facet_core::Def;
1570
1571    match shape.def {
1572        Def::Pointer(pointer_def) => {
1573            if let Some(pointee) = pointer_def.pointee() {
1574                // Recursively dereference in case of nested pointers
1575                deref_pointer(pointee)
1576            } else {
1577                // Opaque pointer - can't dereference
1578                shape
1579            }
1580        }
1581        _ => shape,
1582    }
1583}
1584
1585/// Check if a shape represents a scalar type.
1586/// Transparently handles pointer types like `Box<i32>`.
1587fn is_scalar_shape(shape: &'static Shape) -> bool {
1588    let shape = deref_pointer(shape);
1589    shape.scalar_type().is_some()
1590}
1591
1592/// Returns the arity of a tuple struct/tuple shape, if applicable.
1593/// Transparently handles pointer types like `Box<(i32, i32)>`.
1594fn tuple_struct_arity(shape: &'static Shape) -> Option<usize> {
1595    use facet_core::{StructKind, Type, UserType};
1596
1597    let shape = deref_pointer(shape);
1598    match shape.ty {
1599        Type::User(UserType::Struct(struct_type)) => match struct_type.kind {
1600            StructKind::Tuple | StructKind::TupleStruct => Some(struct_type.fields.len()),
1601            _ => None,
1602        },
1603        _ => None,
1604    }
1605}
1606
1607/// Returns true if the shape is a named struct (non-tuple).
1608/// Transparently handles pointer types like `Box<MyStruct>`.
1609fn is_named_struct_shape(shape: &'static Shape) -> bool {
1610    use facet_core::{StructKind, Type, UserType};
1611
1612    let shape = deref_pointer(shape);
1613    matches!(
1614        shape.ty,
1615        Type::User(UserType::Struct(struct_type)) if matches!(struct_type.kind, StructKind::Struct)
1616    )
1617}
1618
1619/// Returns true if the shape is a sequence type (List, Array, Slice, Set).
1620/// These types serialize as arrays/sequences in formats like TOML, JSON, YAML.
1621/// Transparently handles pointer types like `Box<Vec<i32>>`.
1622fn is_sequence_shape(shape: &'static Shape) -> bool {
1623    use facet_core::Def;
1624
1625    let shape = deref_pointer(shape);
1626    matches!(
1627        shape.def,
1628        Def::List(_) | Def::Array(_) | Def::Slice(_) | Def::Set(_)
1629    )
1630}
1631
1632/// Returns true if the shape is a unit-only enum.
1633/// Unit-only enums serialize as strings in most formats (TOML, JSON, YAML).
1634/// Transparently handles pointer types like `Box<UnitEnum>`.
1635fn is_unit_enum_shape(shape: &'static Shape) -> bool {
1636    use facet_core::{Type, UserType};
1637
1638    let shape = deref_pointer(shape);
1639    match shape.ty {
1640        Type::User(UserType::Enum(enum_type)) => {
1641            // Check if all variants are unit variants
1642            enum_type.variants.iter().all(|v| v.data.fields.is_empty())
1643        }
1644        _ => false,
1645    }
1646}
1647
1648/// Information about variants grouped by their expected format.
1649///
1650/// Used by deserializers to efficiently dispatch untagged enum parsing
1651/// based on the type of value encountered.
1652#[derive(Debug, Default)]
1653pub struct VariantsByFormat {
1654    /// Variants that expect a scalar value (newtype wrapping String, i32, etc.)
1655    ///
1656    /// **Deprecated:** Use the type-specific fields below for better type matching.
1657    /// This field contains all scalar variants regardless of type.
1658    pub scalar_variants: Vec<(&'static Variant, &'static Shape)>,
1659
1660    /// Variants that expect a boolean value (newtype wrapping bool)
1661    pub bool_variants: Vec<(&'static Variant, &'static Shape)>,
1662
1663    /// Variants that expect an integer value (newtype wrapping i8, u8, i32, u64, etc.)
1664    pub int_variants: Vec<(&'static Variant, &'static Shape)>,
1665
1666    /// Variants that expect a float value (newtype wrapping f32, f64)
1667    pub float_variants: Vec<(&'static Variant, &'static Shape)>,
1668
1669    /// Variants that expect a string value (newtype wrapping String, `&str`, `Cow<str>`)
1670    pub string_variants: Vec<(&'static Variant, &'static Shape)>,
1671
1672    /// Variants that expect a sequence (tuple variants)
1673    /// Grouped by arity for efficient matching.
1674    pub tuple_variants: Vec<(&'static Variant, usize)>,
1675
1676    /// Variants that expect a mapping (struct variants, newtype wrapping struct)
1677    pub struct_variants: Vec<&'static Variant>,
1678
1679    /// Unit variants (no data)
1680    pub unit_variants: Vec<&'static Variant>,
1681
1682    /// Other variants that don't fit the above categories
1683    pub other_variants: Vec<&'static Variant>,
1684}
1685
1686impl VariantsByFormat {
1687    /// Build variant classification for an enum shape.
1688    ///
1689    /// Returns None if the shape is not an enum.
1690    pub fn from_shape(shape: &'static Shape) -> Option<Self> {
1691        use facet_core::{Type, UserType};
1692
1693        let enum_type = match shape.ty {
1694            Type::User(UserType::Enum(e)) => e,
1695            _ => return None,
1696        };
1697
1698        let mut result = Self::default();
1699
1700        for variant in enum_type.variants {
1701            match VariantFormat::from_variant(variant) {
1702                VariantFormat::Unit => {
1703                    result.unit_variants.push(variant);
1704                }
1705                VariantFormat::NewtypeScalar { inner_shape } => {
1706                    // Add to general scalar_variants (for backward compatibility)
1707                    result.scalar_variants.push((variant, inner_shape));
1708
1709                    // Classify by specific scalar type for better type matching
1710                    // Dereference through pointers (Box, &, etc.) to get the actual scalar type
1711                    use facet_core::ScalarType;
1712                    let scalar_shape = deref_pointer(inner_shape);
1713                    match scalar_shape.scalar_type() {
1714                        Some(ScalarType::Bool) => {
1715                            result.bool_variants.push((variant, inner_shape));
1716                        }
1717                        Some(
1718                            ScalarType::U8
1719                            | ScalarType::U16
1720                            | ScalarType::U32
1721                            | ScalarType::U64
1722                            | ScalarType::U128
1723                            | ScalarType::USize
1724                            | ScalarType::I8
1725                            | ScalarType::I16
1726                            | ScalarType::I32
1727                            | ScalarType::I64
1728                            | ScalarType::I128
1729                            | ScalarType::ISize,
1730                        ) => {
1731                            result.int_variants.push((variant, inner_shape));
1732                        }
1733                        Some(ScalarType::F32 | ScalarType::F64) => {
1734                            result.float_variants.push((variant, inner_shape));
1735                        }
1736                        #[cfg(feature = "alloc")]
1737                        Some(ScalarType::String | ScalarType::CowStr) => {
1738                            result.string_variants.push((variant, inner_shape));
1739                        }
1740                        Some(ScalarType::Str | ScalarType::Char) => {
1741                            result.string_variants.push((variant, inner_shape));
1742                        }
1743                        _ => {
1744                            // Other scalar types (Unit, SocketAddr, IpAddr, etc.) - leave in general scalar_variants only
1745                        }
1746                    }
1747                }
1748                VariantFormat::NewtypeStruct { .. } => {
1749                    result.struct_variants.push(variant);
1750                }
1751                VariantFormat::NewtypeTuple { arity, .. } => {
1752                    result.tuple_variants.push((variant, arity));
1753                }
1754                VariantFormat::NewtypeSequence { .. } => {
1755                    // Sequences like Vec<T> are variable-length, so we use arity 0
1756                    // to indicate "accepts any array" (not an exact match requirement)
1757                    result.tuple_variants.push((variant, 0));
1758                }
1759                VariantFormat::NewtypeOther { .. } => {
1760                    result.other_variants.push(variant);
1761                }
1762                VariantFormat::Tuple { arity } => {
1763                    result.tuple_variants.push((variant, arity));
1764                }
1765                VariantFormat::Struct => {
1766                    result.struct_variants.push(variant);
1767                }
1768            }
1769        }
1770
1771        Some(result)
1772    }
1773
1774    /// Get tuple variants with a specific arity.
1775    pub fn tuple_variants_with_arity(&self, arity: usize) -> Vec<&'static Variant> {
1776        self.tuple_variants
1777            .iter()
1778            .filter(|(_, a)| *a == arity)
1779            .map(|(v, _)| *v)
1780            .collect()
1781    }
1782
1783    /// Check if there are any scalar-expecting variants.
1784    pub fn has_scalar_variants(&self) -> bool {
1785        !self.scalar_variants.is_empty()
1786    }
1787
1788    /// Check if there are any tuple-expecting variants.
1789    pub fn has_tuple_variants(&self) -> bool {
1790        !self.tuple_variants.is_empty()
1791    }
1792
1793    /// Check if there are any struct-expecting variants.
1794    pub fn has_struct_variants(&self) -> bool {
1795        !self.struct_variants.is_empty()
1796    }
1797}
1798
1799// ============================================================================
1800// Schema Builder
1801// ============================================================================
1802
1803/// How enum variants are represented in the serialized format.
1804#[derive(Debug, Clone, PartialEq, Eq, Default)]
1805pub enum EnumRepr {
1806    /// Variant fields are flattened to the same level as other fields.
1807    /// Also used for `#[facet(untagged)]` enums where there's no tag at all.
1808    /// Used by formats like KDL, TOML where all fields appear at one level.
1809    /// Example: `{"name": "...", "host": "...", "port": 8080}`
1810    #[default]
1811    Flattened,
1812
1813    /// Variant name is a key, variant content is nested under it.
1814    /// This is the default serde representation for enums.
1815    /// Example: `{"name": "...", "Tcp": {"host": "...", "port": 8080}}`
1816    ExternallyTagged,
1817
1818    /// Tag field is inside the content, alongside variant fields.
1819    /// Used with `#[facet(tag = "type")]`.
1820    /// Example: `{"type": "Tcp", "host": "...", "port": 8080}`
1821    InternallyTagged {
1822        /// The name of the tag field (e.g., "type")
1823        tag: &'static str,
1824    },
1825
1826    /// Tag and content are adjacent fields at the same level.
1827    /// Used with `#[facet(tag = "t", content = "c")]`.
1828    /// Example: `{"t": "Tcp", "c": {"host": "...", "port": 8080}}`
1829    AdjacentlyTagged {
1830        /// The name of the tag field (e.g., "t")
1831        tag: &'static str,
1832        /// The name of the content field (e.g., "c")
1833        content: &'static str,
1834    },
1835}
1836
1837impl EnumRepr {
1838    /// Detect the enum representation from a Shape's attributes.
1839    ///
1840    /// Returns:
1841    /// - `Flattened` if `#[facet(untagged)]`
1842    /// - `InternallyTagged` if `#[facet(tag = "...")]` without content
1843    /// - `AdjacentlyTagged` if both `#[facet(tag = "...", content = "...")]`
1844    /// - `ExternallyTagged` if no attributes (the default enum representation)
1845    pub fn from_shape(shape: &'static Shape) -> Self {
1846        let tag = shape.get_tag_attr();
1847        let content = shape.get_content_attr();
1848        let untagged = shape.is_untagged();
1849
1850        match (tag, content, untagged) {
1851            // Untagged explicitly requested
1852            (_, _, true) => EnumRepr::Flattened,
1853            // Both tag and content specified → adjacently tagged
1854            (Some(t), Some(c), false) => EnumRepr::AdjacentlyTagged { tag: t, content: c },
1855            // Only tag specified → internally tagged
1856            (Some(t), None, false) => EnumRepr::InternallyTagged { tag: t },
1857            // No attributes → default to externally tagged (variant name as key)
1858            (None, None, false) => EnumRepr::ExternallyTagged,
1859            // Content without tag is invalid, treat as externally tagged
1860            (None, Some(_), false) => EnumRepr::ExternallyTagged,
1861        }
1862    }
1863}
1864
1865impl Schema {
1866    /// Build a schema for the given shape with flattened enum representation.
1867    ///
1868    /// Returns an error if the type definition contains conflicts, such as
1869    /// duplicate field names from parent and flattened structs.
1870    ///
1871    /// Note: This defaults to `Flattened` representation. For auto-detection
1872    /// based on `#[facet(tag = "...")]` attributes, use [`Schema::build_auto`].
1873    pub fn build(shape: &'static Shape) -> Result<Self, SchemaError> {
1874        Self::build_with_repr(shape, EnumRepr::Flattened)
1875    }
1876
1877    /// Build a schema with auto-detected enum representation based on each enum's attributes.
1878    ///
1879    /// This inspects each flattened enum's shape attributes to determine its representation:
1880    /// - `#[facet(untagged)]` → Flattened
1881    /// - `#[facet(tag = "type")]` → InternallyTagged
1882    /// - `#[facet(tag = "t", content = "c")]` → AdjacentlyTagged
1883    /// - No attributes → Flattened (for flatten solver behavior)
1884    ///
1885    /// For externally-tagged enums (variant name as key), use [`Schema::build_externally_tagged`].
1886    pub fn build_auto(shape: &'static Shape) -> Result<Self, SchemaError> {
1887        let builder = SchemaBuilder::new(shape, EnumRepr::Flattened).with_auto_detect();
1888        builder.into_schema()
1889    }
1890
1891    /// Build a schema for externally-tagged enum representation (e.g., JSON).
1892    ///
1893    /// In this representation, the variant name appears as a key and the
1894    /// variant's content is nested under it. The solver will only expect
1895    /// to see the variant name as a top-level key, not the variant's fields.
1896    pub fn build_externally_tagged(shape: &'static Shape) -> Result<Self, SchemaError> {
1897        Self::build_with_repr(shape, EnumRepr::ExternallyTagged)
1898    }
1899
1900    /// Build a schema with the specified enum representation.
1901    pub fn build_with_repr(shape: &'static Shape, repr: EnumRepr) -> Result<Self, SchemaError> {
1902        let builder = SchemaBuilder::new(shape, repr);
1903        builder.into_schema()
1904    }
1905
1906    /// Get the resolutions for this schema.
1907    pub fn resolutions(&self) -> &[Resolution] {
1908        &self.resolutions
1909    }
1910}
1911
1912struct SchemaBuilder {
1913    shape: &'static Shape,
1914    enum_repr: EnumRepr,
1915    /// If true, detect enum representation from each enum's shape attributes.
1916    /// If false, use `enum_repr` for all enums.
1917    auto_detect_enum_repr: bool,
1918}
1919
1920impl SchemaBuilder {
1921    fn new(shape: &'static Shape, enum_repr: EnumRepr) -> Self {
1922        Self {
1923            shape,
1924            enum_repr,
1925            auto_detect_enum_repr: false,
1926        }
1927    }
1928
1929    fn with_auto_detect(mut self) -> Self {
1930        self.auto_detect_enum_repr = true;
1931        self
1932    }
1933
1934    fn analyze(&self) -> Result<Vec<Resolution>, SchemaError> {
1935        self.analyze_shape(self.shape, FieldPath::empty(), Vec::new())
1936    }
1937
1938    /// Analyze a shape and return all possible resolutions.
1939    /// Returns a Vec because enums create multiple resolutions.
1940    ///
1941    /// - `current_path`: The internal field path (for FieldInfo)
1942    /// - `key_prefix`: The serialized key path prefix (for known_paths)
1943    fn analyze_shape(
1944        &self,
1945        shape: &'static Shape,
1946        current_path: FieldPath,
1947        key_prefix: KeyPath,
1948    ) -> Result<Vec<Resolution>, SchemaError> {
1949        match shape.ty {
1950            Type::User(UserType::Struct(struct_type)) => {
1951                self.analyze_struct(struct_type, current_path, key_prefix)
1952            }
1953            Type::User(UserType::Enum(enum_type)) => {
1954                // Enum at root level: create one configuration per variant
1955                self.analyze_enum(shape, enum_type, current_path, key_prefix)
1956            }
1957            _ => {
1958                // For non-struct types at root level, return single empty config
1959                Ok(vec![Resolution::new()])
1960            }
1961        }
1962    }
1963
1964    /// Analyze an enum and return one configuration per variant.
1965    ///
1966    /// - `current_path`: The internal field path (for FieldInfo)
1967    /// - `key_prefix`: The serialized key path prefix (for known_paths)
1968    fn analyze_enum(
1969        &self,
1970        shape: &'static Shape,
1971        enum_type: facet_core::EnumType,
1972        current_path: FieldPath,
1973        key_prefix: KeyPath,
1974    ) -> Result<Vec<Resolution>, SchemaError> {
1975        let enum_name = shape.type_identifier;
1976        let mut result = Vec::new();
1977
1978        for variant in enum_type.variants {
1979            let mut config = Resolution::new();
1980
1981            // Record this variant selection
1982            config.add_variant_selection(current_path.clone(), enum_name, variant.name);
1983
1984            let variant_path = current_path.push_variant("", variant.name);
1985
1986            // Get resolutions from the variant's content
1987            let variant_configs =
1988                self.analyze_variant_content(variant, &variant_path, &key_prefix)?;
1989
1990            // Merge each variant config into the base
1991            for variant_config in variant_configs {
1992                let mut final_config = config.clone();
1993                final_config.merge(&variant_config)?;
1994                result.push(final_config);
1995            }
1996        }
1997
1998        Ok(result)
1999    }
2000
2001    /// Analyze a struct and return all possible resolutions.
2002    ///
2003    /// - `current_path`: The internal field path (for FieldInfo)
2004    /// - `key_prefix`: The serialized key path prefix (for known_paths)
2005    fn analyze_struct(
2006        &self,
2007        struct_type: StructType,
2008        current_path: FieldPath,
2009        key_prefix: KeyPath,
2010    ) -> Result<Vec<Resolution>, SchemaError> {
2011        // Start with one empty configuration
2012        let mut configs = vec![Resolution::new()];
2013
2014        // Process each field, potentially multiplying resolutions
2015        for field in struct_type.fields {
2016            configs =
2017                self.analyze_field_into_configs(field, &current_path, &key_prefix, configs)?;
2018        }
2019
2020        Ok(configs)
2021    }
2022
2023    /// Process a field and return updated resolutions.
2024    /// If the field is a flattened enum, this may multiply the number of configs.
2025    ///
2026    /// - `parent_path`: The internal field path to the parent (for FieldInfo)
2027    /// - `key_prefix`: The serialized key path prefix (for known_paths)
2028    fn analyze_field_into_configs(
2029        &self,
2030        field: &'static Field,
2031        parent_path: &FieldPath,
2032        key_prefix: &KeyPath,
2033        mut configs: Vec<Resolution>,
2034    ) -> Result<Vec<Resolution>, SchemaError> {
2035        let is_flatten = field.is_flattened();
2036
2037        if is_flatten {
2038            // Flattened: inner keys bubble up to current level (same key_prefix)
2039            self.analyze_flattened_field_into_configs(field, parent_path, key_prefix, configs)
2040        } else {
2041            // Regular field: add to ALL current configs
2042            let field_path = parent_path.push_field(field.name);
2043            let required = !field.has_default() && !is_option_type(field.shape());
2044
2045            // Build the key path for this field
2046            let mut field_key_path = key_prefix.clone();
2047            field_key_path.push(field.name);
2048
2049            let field_info = FieldInfo {
2050                serialized_name: field.name,
2051                path: field_path,
2052                required,
2053                value_shape: field.shape(),
2054                field,
2055            };
2056
2057            for config in &mut configs {
2058                config.add_field(field_info.clone())?;
2059                // Add this field's key path
2060                config.add_key_path(field_key_path.clone());
2061            }
2062
2063            // If the field's value is a struct, recurse to collect nested key paths
2064            // (for probing, not for flattening - these are nested in serialized format)
2065            // This may fork resolutions if the nested struct contains flattened enums!
2066            configs =
2067                self.collect_nested_key_paths_for_shape(field.shape(), &field_key_path, configs)?;
2068
2069            Ok(configs)
2070        }
2071    }
2072
2073    /// Collect nested key paths from a shape into resolutions.
2074    /// This handles the case where a non-flattened field contains a struct with flattened enums.
2075    /// Returns updated resolutions (may fork if flattened enums are encountered).
2076    fn collect_nested_key_paths_for_shape(
2077        &self,
2078        shape: &'static Shape,
2079        key_prefix: &KeyPath,
2080        configs: Vec<Resolution>,
2081    ) -> Result<Vec<Resolution>, SchemaError> {
2082        match shape.ty {
2083            Type::User(UserType::Struct(struct_type)) => {
2084                self.collect_nested_key_paths_for_struct(struct_type, key_prefix, configs)
2085            }
2086            _ => Ok(configs),
2087        }
2088    }
2089
2090    /// Collect nested key paths from a struct, potentially forking for flattened enums.
2091    fn collect_nested_key_paths_for_struct(
2092        &self,
2093        struct_type: StructType,
2094        key_prefix: &KeyPath,
2095        mut configs: Vec<Resolution>,
2096    ) -> Result<Vec<Resolution>, SchemaError> {
2097        for field in struct_type.fields {
2098            let is_flatten = field.is_flattened();
2099            let mut field_key_path = key_prefix.clone();
2100
2101            if is_flatten {
2102                // Flattened field: keys bubble up to current level, may fork configs
2103                configs =
2104                    self.collect_nested_key_paths_for_flattened(field, key_prefix, configs)?;
2105            } else {
2106                // Regular field: add key path and recurse
2107                field_key_path.push(field.name);
2108
2109                for config in &mut configs {
2110                    config.add_key_path(field_key_path.clone());
2111                }
2112
2113                // Recurse into nested structs
2114                configs = self.collect_nested_key_paths_for_shape(
2115                    field.shape(),
2116                    &field_key_path,
2117                    configs,
2118                )?;
2119            }
2120        }
2121        Ok(configs)
2122    }
2123
2124    /// Handle flattened fields when collecting nested key paths.
2125    /// This may fork resolutions for flattened enums.
2126    fn collect_nested_key_paths_for_flattened(
2127        &self,
2128        field: &'static Field,
2129        key_prefix: &KeyPath,
2130        configs: Vec<Resolution>,
2131    ) -> Result<Vec<Resolution>, SchemaError> {
2132        let shape = field.shape();
2133
2134        match shape.ty {
2135            Type::User(UserType::Struct(struct_type)) => {
2136                // Flattened struct: recurse with same key_prefix
2137                self.collect_nested_key_paths_for_struct(struct_type, key_prefix, configs)
2138            }
2139            Type::User(UserType::Enum(enum_type)) => {
2140                // Flattened enum: fork resolutions
2141                // We need to match each config to its corresponding variant
2142                let mut result = Vec::new();
2143
2144                for config in configs {
2145                    // Find which variant this config has selected for this field
2146                    let selected_variant = config
2147                        .variant_selections()
2148                        .iter()
2149                        .find(|vs| {
2150                            // Match by the field name in the path
2151                            vs.path.segments().last() == Some(&PathSegment::Field(field.name))
2152                        })
2153                        .map(|vs| vs.variant_name);
2154
2155                    if let Some(variant_name) = selected_variant {
2156                        // Find the variant and collect its key paths
2157                        if let Some(variant) =
2158                            enum_type.variants.iter().find(|v| v.name == variant_name)
2159                        {
2160                            let mut updated_config = config;
2161                            updated_config = self.collect_variant_key_paths(
2162                                variant,
2163                                key_prefix,
2164                                updated_config,
2165                            )?;
2166                            result.push(updated_config);
2167                        } else {
2168                            result.push(config);
2169                        }
2170                    } else {
2171                        result.push(config);
2172                    }
2173                }
2174                Ok(result)
2175            }
2176            _ => Ok(configs),
2177        }
2178    }
2179
2180    /// Collect key paths from an enum variant's content.
2181    fn collect_variant_key_paths(
2182        &self,
2183        variant: &'static Variant,
2184        key_prefix: &KeyPath,
2185        mut config: Resolution,
2186    ) -> Result<Resolution, SchemaError> {
2187        // Check if this is a newtype variant (single unnamed field)
2188        if variant.data.fields.len() == 1 && variant.data.fields[0].name == "0" {
2189            let inner_field = &variant.data.fields[0];
2190            let inner_shape = inner_field.shape();
2191
2192            // If the inner type is a struct, flatten its fields
2193            if let Type::User(UserType::Struct(inner_struct)) = inner_shape.ty {
2194                let configs = self.collect_nested_key_paths_for_struct(
2195                    inner_struct,
2196                    key_prefix,
2197                    vec![config],
2198                )?;
2199                return Ok(configs.into_iter().next().unwrap_or_else(Resolution::new));
2200            }
2201        }
2202
2203        // Named fields - process each
2204        for variant_field in variant.data.fields {
2205            let is_flatten = variant_field.is_flattened();
2206
2207            if is_flatten {
2208                let configs = self.collect_nested_key_paths_for_flattened(
2209                    variant_field,
2210                    key_prefix,
2211                    vec![config],
2212                )?;
2213                config = configs.into_iter().next().unwrap_or_else(Resolution::new);
2214            } else {
2215                let mut field_key_path = key_prefix.clone();
2216                field_key_path.push(variant_field.name);
2217                config.add_key_path(field_key_path.clone());
2218
2219                let configs = self.collect_nested_key_paths_for_shape(
2220                    variant_field.shape(),
2221                    &field_key_path,
2222                    vec![config],
2223                )?;
2224                config = configs.into_iter().next().unwrap_or_else(Resolution::new);
2225            }
2226        }
2227        Ok(config)
2228    }
2229
2230    /// Collect ONLY key paths from a variant's content (no fields added).
2231    /// Used for externally-tagged enums where variant content is nested and
2232    /// will be parsed separately by the deserializer.
2233    fn collect_variant_key_paths_only(
2234        &self,
2235        variant: &'static Variant,
2236        key_prefix: &KeyPath,
2237        config: &mut Resolution,
2238    ) -> Result<(), SchemaError> {
2239        // Check if this is a newtype variant (single unnamed field)
2240        if variant.data.fields.len() == 1 && variant.data.fields[0].name == "0" {
2241            let inner_field = &variant.data.fields[0];
2242            let inner_shape = inner_field.shape();
2243
2244            // If the inner type is a struct, add key paths for its fields
2245            if let Type::User(UserType::Struct(inner_struct)) = inner_shape.ty {
2246                Self::collect_struct_key_paths_only(inner_struct, key_prefix, config);
2247                return Ok(());
2248            }
2249        }
2250
2251        // Named fields - add key paths for each
2252        for variant_field in variant.data.fields {
2253            let mut field_key_path = key_prefix.clone();
2254            field_key_path.push(variant_field.name);
2255            config.add_key_path(field_key_path.clone());
2256
2257            // Recurse into nested structs
2258            if let Type::User(UserType::Struct(inner_struct)) = variant_field.shape().ty {
2259                Self::collect_struct_key_paths_only(inner_struct, &field_key_path, config);
2260            }
2261        }
2262        Ok(())
2263    }
2264
2265    /// Recursively collect key paths from a struct (no fields added).
2266    fn collect_struct_key_paths_only(
2267        struct_type: StructType,
2268        key_prefix: &KeyPath,
2269        config: &mut Resolution,
2270    ) {
2271        for field in struct_type.fields {
2272            let is_flatten = field.is_flattened();
2273
2274            if is_flatten {
2275                // Flattened field: keys bubble up to current level
2276                if let Type::User(UserType::Struct(inner_struct)) = field.shape().ty {
2277                    Self::collect_struct_key_paths_only(inner_struct, key_prefix, config);
2278                }
2279            } else {
2280                // Regular field: add its key path
2281                let mut field_key_path = key_prefix.clone();
2282                field_key_path.push(field.name);
2283                config.add_key_path(field_key_path.clone());
2284
2285                // Recurse into nested structs
2286                if let Type::User(UserType::Struct(inner_struct)) = field.shape().ty {
2287                    Self::collect_struct_key_paths_only(inner_struct, &field_key_path, config);
2288                }
2289            }
2290        }
2291    }
2292
2293    /// Process a flattened field, potentially forking resolutions for enums.
2294    ///
2295    /// For flattened fields, the inner keys bubble up to the current level,
2296    /// so we pass the same key_prefix (not key_prefix + field.name).
2297    ///
2298    /// If the field is `Option<T>`, we unwrap to get T and mark all resulting
2299    /// fields as optional (since the entire flattened block can be omitted).
2300    fn analyze_flattened_field_into_configs(
2301        &self,
2302        field: &'static Field,
2303        parent_path: &FieldPath,
2304        key_prefix: &KeyPath,
2305        configs: Vec<Resolution>,
2306    ) -> Result<Vec<Resolution>, SchemaError> {
2307        let field_path = parent_path.push_field(field.name);
2308        let original_shape = field.shape();
2309
2310        // Check if this is Option<T> - if so, unwrap and mark all fields optional
2311        let (shape, is_optional_flatten) = match unwrap_option_type(original_shape) {
2312            Some(inner) => (inner, true),
2313            None => (original_shape, false),
2314        };
2315
2316        match shape.ty {
2317            Type::User(UserType::Struct(struct_type)) => {
2318                // Flatten a struct: get its resolutions and merge into each of ours
2319                // Key prefix stays the same - inner keys bubble up
2320                let mut struct_configs =
2321                    self.analyze_struct(struct_type, field_path, key_prefix.clone())?;
2322
2323                // If the flatten field was Option<T>, mark all inner fields as optional
2324                if is_optional_flatten {
2325                    for config in &mut struct_configs {
2326                        config.mark_all_optional();
2327                    }
2328                }
2329
2330                // Each of our configs combines with each struct config
2331                // (usually struct_configs has 1 element unless it contains enums)
2332                let mut result = Vec::new();
2333                for base_config in configs {
2334                    for struct_config in &struct_configs {
2335                        let mut merged = base_config.clone();
2336                        merged.merge(struct_config)?;
2337                        result.push(merged);
2338                    }
2339                }
2340                Ok(result)
2341            }
2342            Type::User(UserType::Enum(enum_type)) => {
2343                // Fork: each existing config × each variant
2344                let mut result = Vec::new();
2345                let enum_name = shape.type_identifier;
2346
2347                // Determine enum representation:
2348                // - If auto_detect_enum_repr is enabled, detect from the enum's shape attributes
2349                // - Otherwise, use the global enum_repr setting
2350                let enum_repr = if self.auto_detect_enum_repr {
2351                    EnumRepr::from_shape(shape)
2352                } else {
2353                    self.enum_repr.clone()
2354                };
2355
2356                for base_config in configs {
2357                    for variant in enum_type.variants {
2358                        let mut forked = base_config.clone();
2359                        forked.add_variant_selection(field_path.clone(), enum_name, variant.name);
2360
2361                        let variant_path = field_path.push_variant(field.name, variant.name);
2362
2363                        match &enum_repr {
2364                            EnumRepr::ExternallyTagged => {
2365                                // For externally tagged enums, the variant name is a key
2366                                // at the current level, and its content is nested underneath.
2367                                let mut variant_key_prefix = key_prefix.clone();
2368                                variant_key_prefix.push(variant.name);
2369
2370                                // Add the variant name itself as a known key path
2371                                forked.add_key_path(variant_key_prefix.clone());
2372
2373                                // Add the variant name as a field (the key that selects this variant)
2374                                let variant_field_info = FieldInfo {
2375                                    serialized_name: variant.name,
2376                                    path: variant_path.clone(),
2377                                    required: !is_optional_flatten,
2378                                    value_shape: shape, // The enum shape
2379                                    field,              // The original flatten field
2380                                };
2381                                forked.add_field(variant_field_info)?;
2382
2383                                // For externally-tagged enums, we do NOT add the variant's
2384                                // inner fields to required fields. They're nested and will
2385                                // be parsed separately by the deserializer.
2386                                // Only add them to known_paths for depth-aware probing.
2387                                self.collect_variant_key_paths_only(
2388                                    variant,
2389                                    &variant_key_prefix,
2390                                    &mut forked,
2391                                )?;
2392
2393                                result.push(forked);
2394                            }
2395                            EnumRepr::Flattened => {
2396                                // For flattened/untagged enums, the variant's fields appear at the
2397                                // same level as other fields. The variant name is NOT a key;
2398                                // only the variant's inner fields are keys.
2399
2400                                // Get resolutions from the variant's content
2401                                // Key prefix stays the same - inner keys bubble up
2402                                let mut variant_configs = self.analyze_variant_content(
2403                                    variant,
2404                                    &variant_path,
2405                                    key_prefix,
2406                                )?;
2407
2408                                // If the flatten field was Option<T>, mark all inner fields as optional
2409                                if is_optional_flatten {
2410                                    for config in &mut variant_configs {
2411                                        config.mark_all_optional();
2412                                    }
2413                                }
2414
2415                                // Merge each variant config into the forked base
2416                                for variant_config in variant_configs {
2417                                    let mut final_config = forked.clone();
2418                                    final_config.merge(&variant_config)?;
2419                                    result.push(final_config);
2420                                }
2421                            }
2422                            EnumRepr::InternallyTagged { tag } => {
2423                                // For internally tagged enums, the tag field appears at the
2424                                // same level as the variant's fields.
2425                                // Example: {"type": "Tcp", "host": "...", "port": 8080}
2426
2427                                // Add the tag field as a known key path
2428                                let mut tag_key_path = key_prefix.clone();
2429                                tag_key_path.push(tag);
2430                                forked.add_key_path(tag_key_path);
2431
2432                                // Add the tag field info - the tag discriminates the variant
2433                                // We use a synthetic field for the tag
2434                                let tag_field_info = FieldInfo {
2435                                    serialized_name: tag,
2436                                    path: variant_path.clone(),
2437                                    required: !is_optional_flatten,
2438                                    value_shape: shape, // The enum shape
2439                                    field,              // The original flatten field
2440                                };
2441                                forked.add_field(tag_field_info)?;
2442
2443                                // Get resolutions from the variant's content
2444                                // Key prefix stays the same - inner keys are at the same level
2445                                let mut variant_configs = self.analyze_variant_content(
2446                                    variant,
2447                                    &variant_path,
2448                                    key_prefix,
2449                                )?;
2450
2451                                // If the flatten field was Option<T>, mark all inner fields as optional
2452                                if is_optional_flatten {
2453                                    for config in &mut variant_configs {
2454                                        config.mark_all_optional();
2455                                    }
2456                                }
2457
2458                                // Merge each variant config into the forked base
2459                                for variant_config in variant_configs {
2460                                    let mut final_config = forked.clone();
2461                                    final_config.merge(&variant_config)?;
2462                                    result.push(final_config);
2463                                }
2464                            }
2465                            EnumRepr::AdjacentlyTagged { tag, content } => {
2466                                // For adjacently tagged enums, both tag and content fields
2467                                // appear at the same level. Content contains the variant's fields.
2468                                // Example: {"t": "Tcp", "c": {"host": "...", "port": 8080}}
2469
2470                                // Add the tag field as a known key path
2471                                let mut tag_key_path = key_prefix.clone();
2472                                tag_key_path.push(tag);
2473                                forked.add_key_path(tag_key_path);
2474
2475                                // Add the tag field info
2476                                let tag_field_info = FieldInfo {
2477                                    serialized_name: tag,
2478                                    path: variant_path.clone(),
2479                                    required: !is_optional_flatten,
2480                                    value_shape: shape, // The enum shape
2481                                    field,              // The original flatten field
2482                                };
2483                                forked.add_field(tag_field_info)?;
2484
2485                                // Add the content field as a known key path
2486                                let mut content_key_prefix = key_prefix.clone();
2487                                content_key_prefix.push(content);
2488                                forked.add_key_path(content_key_prefix.clone());
2489
2490                                // The variant's fields are nested under the content key
2491                                // Collect key paths for probing
2492                                self.collect_variant_key_paths_only(
2493                                    variant,
2494                                    &content_key_prefix,
2495                                    &mut forked,
2496                                )?;
2497
2498                                result.push(forked);
2499                            }
2500                        }
2501                    }
2502                }
2503                Ok(result)
2504            }
2505            _ => {
2506                // Can't flatten other types - treat as regular field
2507                // For Option<T> flatten, also consider optionality from the wrapper
2508                let required =
2509                    !field.has_default() && !is_option_type(shape) && !is_optional_flatten;
2510
2511                // For non-flattenable types, add the field with its key path
2512                let mut field_key_path = key_prefix.clone();
2513                field_key_path.push(field.name);
2514
2515                let field_info = FieldInfo {
2516                    serialized_name: field.name,
2517                    path: field_path,
2518                    required,
2519                    value_shape: shape,
2520                    field,
2521                };
2522
2523                let mut result = configs;
2524                for config in &mut result {
2525                    config.add_field(field_info.clone())?;
2526                    config.add_key_path(field_key_path.clone());
2527                }
2528                Ok(result)
2529            }
2530        }
2531    }
2532
2533    /// Analyze a variant's content and return resolutions.
2534    ///
2535    /// - `variant_path`: The internal field path (for FieldInfo)
2536    /// - `key_prefix`: The serialized key path prefix (for known_paths)
2537    fn analyze_variant_content(
2538        &self,
2539        variant: &'static Variant,
2540        variant_path: &FieldPath,
2541        key_prefix: &KeyPath,
2542    ) -> Result<Vec<Resolution>, SchemaError> {
2543        // Check if this is a newtype variant (single unnamed field like `Foo(Bar)`)
2544        if variant.data.fields.len() == 1 && variant.data.fields[0].name == "0" {
2545            let inner_field = &variant.data.fields[0];
2546            let inner_shape = inner_field.shape();
2547
2548            // If the inner type is a struct, treat the newtype wrapper as transparent.
2549            //
2550            // Previously we pushed a synthetic `"0"` segment onto the path. That made the
2551            // solver think there was an extra field between the variant and the inner
2552            // struct (e.g., `backend.backend::Local.0.cache`). KDL flattening does not
2553            // expose that tuple wrapper, so the deserializer would try to open a field
2554            // named `"0"` on the inner struct/enum, causing "no such field" errors when
2555            // navigating paths like `backend::Local.cache`.
2556            //
2557            // Keep the synthetic `"0"` segment so the solver/reflect layer walks through
2558            // the tuple wrapper that Rust generates for newtype variants.
2559
2560            // For untagged enum variant resolution, we need to look at the "effective"
2561            // shape that determines the serialization format. This unwraps:
2562            // 1. Transparent wrappers (shape.inner) - e.g., `Curve64(GCurve<f64, f64>)`
2563            // 2. Proxy types (shape.proxy) - e.g., `GCurve` uses `GCurveProxy` for ser/de
2564            //
2565            // This ensures that `{"x":..., "y":...}` correctly matches `Linear(Curve64)`
2566            // where Curve64 is transparent around GCurve which has a proxy with x,y fields.
2567            let effective_shape = unwrap_to_effective_shape(inner_shape);
2568
2569            if let Type::User(UserType::Struct(inner_struct)) = effective_shape.ty {
2570                let inner_path = variant_path.push_field("0");
2571                return self.analyze_struct(inner_struct, inner_path, key_prefix.clone());
2572            }
2573        }
2574
2575        // Named fields or multiple fields - analyze as a pseudo-struct
2576        let mut configs = vec![Resolution::new()];
2577        for variant_field in variant.data.fields {
2578            configs =
2579                self.analyze_field_into_configs(variant_field, variant_path, key_prefix, configs)?;
2580        }
2581        Ok(configs)
2582    }
2583
2584    fn into_schema(self) -> Result<Schema, SchemaError> {
2585        let resolutions = self.analyze()?;
2586        let num_resolutions = resolutions.len();
2587
2588        // Build inverted index: field_name → bitmask of config indices
2589        let mut field_to_resolutions: BTreeMap<&'static str, ResolutionSet> = BTreeMap::new();
2590        for (idx, config) in resolutions.iter().enumerate() {
2591            for field_name in config.fields().keys() {
2592                field_to_resolutions
2593                    .entry(*field_name)
2594                    .or_insert_with(|| ResolutionSet::empty(num_resolutions))
2595                    .insert(idx);
2596            }
2597        }
2598
2599        Ok(Schema {
2600            shape: self.shape,
2601            resolutions,
2602            field_to_resolutions,
2603        })
2604    }
2605}
2606
2607/// Check if a shape represents an Option type.
2608fn is_option_type(shape: &'static Shape) -> bool {
2609    matches!(shape.def, Def::Option(_))
2610}
2611
2612/// If shape is `Option<T>`, returns `Some(T's shape)`. Otherwise returns `None`.
2613fn unwrap_option_type(shape: &'static Shape) -> Option<&'static Shape> {
2614    match shape.def {
2615        Def::Option(option_def) => Some(option_def.t),
2616        _ => None,
2617    }
2618}
2619
2620/// Unwrap transparent wrappers and proxies to get the effective shape for field matching.
2621///
2622/// When determining which untagged enum variant matches a set of fields, we need to
2623/// look at the "effective" shape that determines the serialization format:
2624///
2625/// 1. Transparent wrappers (shape.inner): e.g., `Curve64` wraps `GCurve<f64, f64>`
2626///    - The wrapper has no serialization presence; it serializes as its inner type
2627///
2628/// 2. Proxy types (shape.proxy): e.g., `GCurve` uses `GCurveProxy` for ser/de
2629///    - The proxy's fields are what appear in the serialized format
2630///
2631/// This function recursively unwraps these layers to find the shape whose fields
2632/// should be used for variant matching. For example:
2633/// - `Curve64` (transparent) → `GCurve<f64, f64>` (has proxy) → `GCurveProxy<f64, f64>`
2634fn unwrap_to_effective_shape(shape: &'static Shape) -> &'static Shape {
2635    // First, unwrap transparent wrappers
2636    let shape = unwrap_transparent(shape);
2637
2638    // Then, if there's a proxy, use its shape instead
2639    if let Some(proxy_def) = shape.proxy {
2640        // Recursively unwrap in case the proxy is also transparent or has its own proxy
2641        unwrap_to_effective_shape(proxy_def.shape)
2642    } else {
2643        shape
2644    }
2645}
2646
2647/// Recursively unwrap transparent wrappers to get to the innermost type.
2648fn unwrap_transparent(shape: &'static Shape) -> &'static Shape {
2649    if let Some(inner) = shape.inner {
2650        unwrap_transparent(inner)
2651    } else {
2652        shape
2653    }
2654}