1use std::collections::HashSet;
2
3use crate::JsonValue;
4use crate::encode::normalize::{is_empty_object, is_json_object};
5use crate::options::{KeyFoldingMode, ResolvedEncodeOptions};
6use crate::shared::constants::DOT;
7use crate::shared::validation::is_identifier_segment;
8
9#[derive(Debug, Clone)]
10pub struct FoldResult {
11 pub folded_key: String,
12 pub remainder: Option<JsonValue>,
13 pub leaf_value: JsonValue,
14 pub segment_count: usize,
15}
16
17#[must_use]
18#[allow(clippy::implicit_hasher)]
19pub fn try_fold_key_chain(
20 key: &str,
21 value: &JsonValue,
22 siblings: &[&str],
23 options: &ResolvedEncodeOptions,
24 root_literal_keys: Option<&HashSet<String>>,
25 path_prefix: Option<&str>,
26 flatten_depth: usize,
27) -> Option<FoldResult> {
28 if options.key_folding != KeyFoldingMode::Safe {
29 return None;
30 }
31
32 if !is_json_object(value) {
33 return None;
34 }
35
36 let effective_depth = flatten_depth;
37 if effective_depth < 2 {
38 return None;
39 }
40
41 let (segments, tail, leaf_value) = collect_single_key_chain(key, value, effective_depth);
42
43 if segments.len() < 2 {
44 return None;
45 }
46
47 if !segments.iter().all(|seg| is_identifier_segment(seg)) {
48 return None;
49 }
50
51 let mut folded_key =
52 String::with_capacity(segments.iter().map(String::len).sum::<usize>() + segments.len());
53 for (i, seg) in segments.iter().enumerate() {
54 if i > 0 {
55 folded_key.push(DOT);
56 }
57 folded_key.push_str(seg);
58 }
59
60 if siblings.iter().any(|sibling| *sibling == folded_key) {
61 return None;
62 }
63
64 let absolute_path = path_prefix.map_or_else(
65 || folded_key.clone(),
66 |prefix| format!("{prefix}{DOT}{folded_key}"),
67 );
68
69 if let Some(root_keys) = root_literal_keys {
70 if root_keys.contains(&absolute_path) {
71 return None;
72 }
73 }
74
75 Some(FoldResult {
76 folded_key,
77 remainder: tail,
78 leaf_value,
79 segment_count: segments.len(),
80 })
81}
82
83fn collect_single_key_chain(
84 start_key: &str,
85 start_value: &JsonValue,
86 max_depth: usize,
87) -> (Vec<String>, Option<JsonValue>, JsonValue) {
88 let mut segments = vec![start_key.to_string()];
89 let mut current_value = start_value.clone();
90
91 while segments.len() < max_depth {
92 let JsonValue::Object(ref obj) = current_value else {
93 break;
94 };
95
96 if obj.len() != 1 {
97 break;
98 }
99
100 let (next_key, next_value) = obj[0].clone();
101 segments.push(next_key);
102 current_value = next_value;
103 }
104
105 match current_value {
106 JsonValue::Object(entries) if is_empty_object(&entries) => {
107 let obj = JsonValue::Object(entries);
108 (segments, None, obj)
109 }
110 JsonValue::Object(entries) => {
111 let remainder = JsonValue::Object(entries);
112 (segments, Some(remainder.clone()), remainder)
113 }
114 other => (segments, None, other),
115 }
116}