facet_solver/
lib.rs

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