Skip to main content

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