1use super::JSONEval;
2use crate::jsoneval::path_utils;
3use crate::jsoneval::types::{LayoutOverlayEntry, ResolvedLayoutResult};
4use crate::time_block;
5
6use indexmap::IndexMap;
7use serde_json::Value;
8
9impl JSONEval {
10 pub fn resolve_layout(&mut self, evaluate: bool) -> Result<ResolvedLayoutResult, String> {
19 if evaluate {
20 let data_str = serde_json::to_string(&self.data)
21 .map_err(|e| format!("Failed to serialize data: {}", e))?;
22 self.evaluate(&data_str, None, None, None)?;
23 }
24
25 Ok(self.resolve_layout_internal())
26 }
27
28 fn resolve_layout_internal(&mut self) -> ResolvedLayoutResult {
29 time_block!(" resolve_layout_internal()", {
30 let layout_paths = self.layout_paths.clone();
31 let mut all_entries = ResolvedLayoutResult::new();
32
33 time_block!(" resolve_layout_elements", {
36 for layout_path in layout_paths.iter() {
37 let resolved_tree = self.resolve_elements_tree(layout_path);
38 let entries = Self::tree_to_overlays(&resolved_tree, layout_path, false, false);
39 all_entries.extend(entries);
40 }
41 });
42
43 time_block!(" sync_layout_hidden_to_schema", {
45 self.sync_layout_hidden_to_schema(&all_entries);
46 });
47
48 all_entries
49 })
50 }
51
52 fn resolve_elements_tree(&self, layout_elements_path: &str) -> Vec<(Value, String)> {
57 let normalized_path = path_utils::normalize_to_json_pointer(layout_elements_path);
58
59 let elements = if let Some(Value::Array(arr)) = self.schema.pointer(&normalized_path) {
60 arr.clone()
61 } else {
62 return Vec::new();
63 };
64
65 let mut result = Vec::with_capacity(elements.len());
66 for element in elements.into_iter() {
67 let (resolved, schema_ref) = self.resolve_element_ref_recursive(element, "");
68 result.push((resolved, schema_ref));
69 }
70 result
71 }
72
73 fn resolve_element_ref_recursive(&self, element: Value, _path_context: &str) -> (Value, String) {
79 let (mut resolved, ref_path) = self.resolve_element_ref(element);
81
82 if let Value::Object(ref mut map) = resolved {
84 if let Some(Value::Array(elements)) = map.get("elements").cloned() {
85 let mut resolved_nested = Vec::with_capacity(elements.len());
86 for nested_element in elements.into_iter() {
87 let (nested_resolved, _) =
88 self.resolve_element_ref_recursive(nested_element, "");
89 resolved_nested.push(nested_resolved);
90 }
91 map.insert("elements".to_string(), Value::Array(resolved_nested));
92 }
93 }
94
95 (resolved, ref_path)
96 }
97
98 fn resolve_element_ref(&self, element: Value) -> (Value, String) {
101 match element {
102 Value::Object(mut map) => {
103 let has_ref = map.get("$ref").is_some();
104 let ref_path = if has_ref {
105 map.get("$ref").and_then(Value::as_str).unwrap_or("").to_string()
106 } else {
107 String::new()
108 };
109
110 if let Some(Value::String(ref_str)) = map.get("$ref").cloned() {
111 let normalized_path = if ref_str.starts_with('#') || ref_str.starts_with('/') {
113 path_utils::normalize_to_json_pointer(&ref_str).into_owned()
114 } else {
115 let schema_pointer =
116 path_utils::dot_notation_to_schema_pointer(&ref_str);
117 let schema_path =
118 path_utils::normalize_to_json_pointer(&schema_pointer).into_owned();
119
120 if self.evaluated_schema.pointer(&schema_path).is_some() {
121 schema_path
122 } else {
123 format!("/properties/{}", ref_str.replace('.', "/properties/"))
124 }
125 };
126
127 let dotted_path = path_utils::pointer_to_dot_notation(&normalized_path);
130 let last_segment = dotted_path.split('.').last().unwrap_or(&dotted_path);
131
132 map.insert("$fullpath".to_string(), Value::String(dotted_path.clone()));
133 map.insert("$path".to_string(), Value::String(last_segment.to_string()));
134 map.insert("$parentHide".to_string(), Value::Bool(false));
135
136 if let Some(referenced_value) = self.evaluated_schema.pointer(&normalized_path) {
137 let resolved = referenced_value.clone();
138
139 if let Value::Object(mut resolved_map) = resolved {
140 map.remove("$ref");
141
142 if let Some(Value::Object(layout_obj)) = resolved_map.remove("$layout") {
143 let mut result = layout_obj.clone();
144 for (key, value) in resolved_map {
145 if key != "type" || !result.contains_key("type") {
146 result.insert(key, value);
147 }
148 }
149 for (key, value) in map {
151 result.insert(key, value);
152 }
153 return (Value::Object(result), dotted_path);
154 } else {
155 for (key, value) in map {
156 resolved_map.insert(key, value);
157 }
158 return (Value::Object(resolved_map), dotted_path);
159 }
160 } else {
161 return (resolved, dotted_path);
162 }
163 }
164 }
165
166 (Value::Object(map), ref_path)
167 }
168 _ => (element, String::new()),
169 }
170 }
171
172 fn tree_to_overlays(
181 tree: &[(Value, String)],
182 layout_path: &str,
183 parent_hidden: bool,
184 parent_disabled: bool,
185 ) -> ResolvedLayoutResult {
186 let mut entries = ResolvedLayoutResult::new();
187
188 for (idx, (element, ref_path)) in tree.iter().enumerate() {
189 let element_idx = idx;
190 let mut overlay = IndexMap::new();
191
192 if let Value::Object(map) = element {
194 const EXCLUDED: &[&str] = &["$ref", "elements", "properties", "items", "required", "additionalProperties"];
199 for (key, value) in map {
200 if !EXCLUDED.contains(&key.as_str()) {
201 overlay.insert(key.clone(), value.clone());
202 }
203 }
204
205 if !overlay.contains_key("$fullpath") {
207 if !ref_path.is_empty() {
208 let last_segment = ref_path.split('.').last().unwrap_or(ref_path);
210 overlay.insert("$fullpath".to_string(), Value::String(ref_path.clone()));
211 overlay.insert("$path".to_string(), Value::String(last_segment.to_string()));
212 } else {
213 let base = Self::layout_path_to_field_path(layout_path);
220 let fullpath = if base.is_empty() {
221 format!("{}", element_idx)
222 } else {
223 format!("{}.{}", base, element_idx)
224 };
225 let last_segment = fullpath.split('.').last().unwrap_or(&fullpath).to_string();
226 overlay.insert("$fullpath".to_string(), Value::String(fullpath));
227 overlay.insert("$path".to_string(), Value::String(last_segment));
228 }
229 }
230
231 overlay.insert("$parentHide".to_string(), Value::Bool(parent_hidden));
234
235 let mut element_hidden = parent_hidden;
237 let mut element_disabled = parent_disabled;
238
239 if let Some(Value::Object(cond)) = overlay.get("condition") {
241 if let Some(Value::Bool(h)) = cond.get("hidden") {
242 element_hidden = element_hidden || *h;
243 }
244 if let Some(Value::Bool(d)) = cond.get("disabled") {
245 element_disabled = element_disabled || *d;
246 }
247 }
248
249 if let Some(Value::Object(hide)) = overlay.get("hideLayout") {
251 if let Some(Value::Bool(all)) = hide.get("all") {
252 if *all {
253 element_hidden = true;
254 }
255 }
256 }
257
258 let show_condition_cascade = parent_hidden || parent_disabled || element_hidden || element_disabled;
260
261 if show_condition_cascade {
265 let mut merged_cond = serde_json::Map::new();
266 if let Some(Value::Object(existing)) = overlay.get("condition") {
267 for (k, v) in existing.iter() {
268 merged_cond.insert(k.clone(), v.clone());
269 }
270 }
271 if parent_hidden || element_hidden {
273 merged_cond.insert("hidden".to_string(), Value::Bool(true));
274 }
275 if parent_disabled || element_disabled {
276 merged_cond.insert("disabled".to_string(), Value::Bool(true));
277 }
278 overlay.insert("condition".to_string(), Value::Object(merged_cond));
279
280 if (parent_hidden || element_hidden)
282 && (element.get("hideLayout").is_some() || element.get("type").is_some())
283 {
284 let mut hide_layout = if let Some(Value::Object(h)) = element.get("hideLayout")
285 {
286 h.clone()
287 } else {
288 serde_json::Map::new()
289 };
290 hide_layout.insert("all".to_string(), Value::Bool(true));
291 overlay.insert("hideLayout".to_string(), Value::Object(hide_layout));
292 }
293 }
294
295 if let Some(Value::Array(children)) = element.get("elements") {
297 let child_tree: Vec<(Value, String)> = children
299 .iter()
300 .map(|c| {
301 if let Value::Object(m) = c {
302 (c.clone(),
303 m.get("$fullpath")
304 .and_then(Value::as_str)
305 .unwrap_or("")
306 .to_string(),
307 )
308 } else {
309 (c.clone(), String::new())
310 }
311 })
312 .collect();
313
314 let child_layout_path = format!("{}/{}/elements", layout_path.trim_end_matches('/'), element_idx);
315
316 let child_overlays = Self::tree_to_overlays(
318 &child_tree,
319 &child_layout_path,
320 element_hidden,
321 element_disabled,
322 );
323 entries.extend(child_overlays);
324 }
325 }
326
327 entries.push(LayoutOverlayEntry {
328 layout_path: layout_path.to_string(),
329 element_idx,
330 schema_ref_path: ref_path.clone(),
331 overlay,
332 });
333 }
334
335 entries
336 }
337
338 fn sync_layout_hidden_to_schema(&mut self, entries: &[LayoutOverlayEntry]) {
344 self.layout_synced_paths.clear();
345
346 for entry in entries {
347 if let Some(Value::Object(cond)) = entry.overlay.get("condition") {
348 if cond
349 .get("hidden")
350 .and_then(|v| v.as_bool())
351 .unwrap_or(false)
352 && !entry.schema_ref_path.is_empty()
353 {
354 self.sync_hidden_to_schema_at(&entry.schema_ref_path);
355 }
356 }
357 if let Some(Value::Object(hide_layout)) = entry.overlay.get("hideLayout") {
359 if hide_layout
360 .get("all")
361 .and_then(|v| v.as_bool())
362 .unwrap_or(false)
363 && !entry.schema_ref_path.is_empty()
364 {
365 self.sync_hidden_to_schema_at(&entry.schema_ref_path);
366 }
367 }
368 }
369 }
370
371 fn sync_hidden_to_schema_at(&mut self, schema_path_dot: &str) {
376 let schema_pointer = path_utils::dot_notation_to_schema_pointer(schema_path_dot);
377 let normalized_pointer = path_utils::normalize_to_json_pointer(&schema_pointer);
378
379 let target_pointer = if self.evaluated_schema.pointer(&normalized_pointer).is_some() {
380 normalized_pointer.into_owned()
381 } else {
382 format!("/properties{}", normalized_pointer)
383 };
384
385 self.layout_synced_paths.push(target_pointer.clone());
386
387 if let Some(schema_node) = self.evaluated_schema.pointer_mut(&target_pointer) {
388 if let Value::Object(map) = schema_node {
389 let mut condition = if let Some(Value::Object(c)) = map.get("condition") {
390 c.clone()
391 } else {
392 serde_json::Map::new()
393 };
394 condition.insert("hidden".to_string(), Value::Bool(true));
395 map.insert("condition".to_string(), Value::Object(condition));
396 }
397 }
398 }
399
400 fn layout_path_to_field_path(layout_path: &str) -> String {
415 let raw = if layout_path.starts_with("#/") {
417 &layout_path[2..]
418 } else if layout_path.starts_with('#') {
419 &layout_path[1..]
420 } else if layout_path.starts_with('/') {
421 &layout_path[1..]
422 } else {
423 layout_path
424 };
425
426 let parts: Vec<&str> = raw.split('/').collect();
428 let mut out: Vec<&str> = Vec::new();
429 let mut i = 0;
430 while i < parts.len() {
431 let seg = parts[i];
432 match seg {
433 "" | "properties" | "$layout" | "elements" | "additionalProperties" => {}
434 _ => out.push(seg),
435 }
436 i += 1;
437 }
438
439 out.join(".")
440 }
441}