1use std::collections::HashMap;
2use std::sync::Arc;
3
4use super::ColumnDef;
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub struct HeaderGroupSnapshot {
8 pub depth: usize,
9 pub id: Arc<str>,
10 pub headers: Vec<HeaderSnapshot>,
11}
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct HeaderSnapshot {
15 pub id: Arc<str>,
16 pub column_id: Arc<str>,
17 pub depth: usize,
18 pub index: usize,
19 pub is_placeholder: bool,
20 pub placeholder_id: Option<Arc<str>>,
21 pub col_span: usize,
22 pub row_span: usize,
23 pub sub_header_ids: Vec<Arc<str>>,
24}
25
26#[derive(Debug, Clone)]
27struct ColumnMeta {
28 depth: usize,
29 parent: Option<Arc<str>>,
30 children: Vec<Arc<str>>,
31}
32
33#[derive(Debug, Clone)]
34struct HeaderNode {
35 id: Arc<str>,
36 column_id: Arc<str>,
37 depth: usize,
38 index: usize,
39 is_placeholder: bool,
40 placeholder_id: Option<Arc<str>>,
41 col_span: usize,
42 row_span: usize,
43 sub_headers: Vec<usize>,
44}
45
46fn build_column_meta<TData>(
47 columns: &[ColumnDef<TData>],
48 depth: usize,
49 parent: Option<Arc<str>>,
50 out: &mut HashMap<Arc<str>, ColumnMeta>,
51) {
52 for col in columns {
53 let id = col.id.clone();
54 let children: Vec<Arc<str>> = col.columns.iter().map(|c| c.id.clone()).collect();
55 out.insert(
56 id.clone(),
57 ColumnMeta {
58 depth,
59 parent: parent.clone(),
60 children,
61 },
62 );
63 if !col.columns.is_empty() {
64 build_column_meta(&col.columns, depth + 1, Some(id), out);
65 }
66 }
67}
68
69fn column_is_visible(
70 id: &str,
71 meta: &HashMap<Arc<str>, ColumnMeta>,
72 leaf_visible: &dyn Fn(&str) -> bool,
73 cache: &mut HashMap<Arc<str>, bool>,
74) -> bool {
75 if let Some(&v) = cache.get(id) {
76 return v;
77 }
78
79 let Some(m) = meta.get(id) else {
80 let v = leaf_visible(id);
81 cache.insert(Arc::<str>::from(id), v);
82 return v;
83 };
84
85 let v = if m.children.is_empty() {
86 leaf_visible(id)
87 } else {
88 m.children
89 .iter()
90 .any(|child| column_is_visible(child.as_ref(), meta, leaf_visible, cache))
91 };
92
93 cache.insert(Arc::<str>::from(id), v);
94 v
95}
96
97fn find_max_depth<TData>(
98 columns: &[ColumnDef<TData>],
99 depth_1_based: usize,
100 meta: &HashMap<Arc<str>, ColumnMeta>,
101 leaf_visible: &dyn Fn(&str) -> bool,
102 visible_cache: &mut HashMap<Arc<str>, bool>,
103 max_depth: &mut usize,
104) {
105 *max_depth = (*max_depth).max(depth_1_based);
106 for col in columns {
107 if !column_is_visible(col.id.as_ref(), meta, leaf_visible, visible_cache) {
108 continue;
109 }
110 if !col.columns.is_empty() {
111 find_max_depth(
112 &col.columns,
113 depth_1_based + 1,
114 meta,
115 leaf_visible,
116 visible_cache,
117 max_depth,
118 );
119 }
120 }
121}
122
123fn header_group_id(header_family: Option<&str>, depth: usize) -> Arc<str> {
124 if let Some(family) = header_family {
125 Arc::<str>::from(format!("{}_{}", family, depth))
126 } else {
127 Arc::<str>::from(depth.to_string())
128 }
129}
130
131fn header_id(
132 header_family: Option<&str>,
133 depth: usize,
134 column_id: &str,
135 child_header_id: &str,
136) -> Arc<str> {
137 if let Some(family) = header_family {
138 Arc::<str>::from(format!(
139 "{}_{}_{}_{}",
140 family, depth, column_id, child_header_id
141 ))
142 } else {
143 Arc::<str>::from(format!("{}_{}_{}", depth, column_id, child_header_id))
144 }
145}
146
147fn create_header_group(
148 headers_to_group: Vec<usize>,
149 depth: usize,
150 meta: &HashMap<Arc<str>, ColumnMeta>,
151 _leaf_visible: &dyn Fn(&str) -> bool,
152 _visible_cache: &mut HashMap<Arc<str>, bool>,
153 header_family: Option<&str>,
154 header_groups: &mut Vec<(usize, Arc<str>, Vec<usize>)>,
155 arena: &mut Vec<HeaderNode>,
156) {
157 let group_id = header_group_id(header_family, depth);
158 let mut pending_parent_headers: Vec<usize> = Vec::new();
159
160 for &header_to_group_idx in &headers_to_group {
161 let column_id = arena[header_to_group_idx].column_id.clone();
162 let Some(col_meta) = meta.get(column_id.as_ref()) else {
163 continue;
164 };
165
166 let is_leaf_header = col_meta.depth == depth;
167
168 let mut parent_column_id: Arc<str> = column_id.clone();
169 let mut is_placeholder = false;
170
171 if is_leaf_header {
172 if let Some(parent) = col_meta.parent.clone() {
173 parent_column_id = parent;
174 } else {
175 is_placeholder = true;
176 }
177 } else {
178 is_placeholder = true;
179 }
180
181 if let Some(&latest_idx) = pending_parent_headers.last()
182 && arena[latest_idx].column_id.as_ref() == parent_column_id.as_ref()
183 {
184 arena[latest_idx].sub_headers.push(header_to_group_idx);
185 continue;
186 }
187
188 let placeholder_id = if is_placeholder {
189 let count = pending_parent_headers
190 .iter()
191 .filter(|&&idx| arena[idx].column_id.as_ref() == parent_column_id.as_ref())
192 .count();
193 Some(Arc::<str>::from(count.to_string()))
194 } else {
195 None
196 };
197
198 let child_header_id = arena[header_to_group_idx].id.clone();
199
200 let header_idx = arena.len();
201 arena.push(HeaderNode {
202 id: header_id(
203 header_family,
204 depth,
205 parent_column_id.as_ref(),
206 child_header_id.as_ref(),
207 ),
208 column_id: parent_column_id,
209 depth,
210 index: pending_parent_headers.len(),
211 is_placeholder,
212 placeholder_id,
213 col_span: 0,
214 row_span: 0,
215 sub_headers: vec![header_to_group_idx],
216 });
217 pending_parent_headers.push(header_idx);
218 }
219
220 header_groups.push((depth, group_id, headers_to_group));
221
222 if depth > 0 {
223 create_header_group(
224 pending_parent_headers,
225 depth - 1,
226 meta,
227 _leaf_visible,
228 _visible_cache,
229 header_family,
230 header_groups,
231 arena,
232 );
233 }
234}
235
236fn recurse_headers_for_spans(
237 headers: &[usize],
238 meta: &HashMap<Arc<str>, ColumnMeta>,
239 leaf_visible: &dyn Fn(&str) -> bool,
240 visible_cache: &mut HashMap<Arc<str>, bool>,
241 arena: &mut [HeaderNode],
242) -> Vec<(usize, usize)> {
243 let mut out = Vec::new();
244 for &idx in headers {
245 if !column_is_visible(
246 arena[idx].column_id.as_ref(),
247 meta,
248 leaf_visible,
249 visible_cache,
250 ) {
251 continue;
252 }
253
254 let mut col_span = 0usize;
255 let mut row_span = 0usize;
256 let mut child_row_spans = vec![0usize];
257
258 let sub_headers = arena[idx].sub_headers.clone();
259 if !sub_headers.is_empty() {
260 child_row_spans.clear();
261 for (child_col_span, child_row_span) in
262 recurse_headers_for_spans(&sub_headers, meta, leaf_visible, visible_cache, arena)
263 {
264 col_span += child_col_span;
265 child_row_spans.push(child_row_span);
266 }
267 } else {
268 col_span = 1;
269 }
270
271 let min_child_row_span = child_row_spans.into_iter().min().unwrap_or(0);
272 row_span += min_child_row_span;
273
274 arena[idx].col_span = col_span;
275 arena[idx].row_span = row_span;
276
277 out.push((col_span, row_span));
278 }
279 out
280}
281
282fn snapshot_group(headers: &[usize], arena: &[HeaderNode]) -> Vec<HeaderSnapshot> {
283 headers
284 .iter()
285 .copied()
286 .map(|idx| HeaderSnapshot {
287 id: arena[idx].id.clone(),
288 column_id: arena[idx].column_id.clone(),
289 depth: arena[idx].depth,
290 index: arena[idx].index,
291 is_placeholder: arena[idx].is_placeholder,
292 placeholder_id: arena[idx].placeholder_id.clone(),
293 col_span: arena[idx].col_span,
294 row_span: arena[idx].row_span,
295 sub_header_ids: arena[idx]
296 .sub_headers
297 .iter()
298 .map(|&child| arena[child].id.clone())
299 .collect(),
300 })
301 .collect()
302}
303
304pub fn build_header_groups<TData>(
310 all_columns: &[ColumnDef<TData>],
311 columns_to_group: &[Arc<str>],
312 leaf_visible: &dyn Fn(&str) -> bool,
313 header_family: Option<&str>,
314) -> Vec<HeaderGroupSnapshot> {
315 let mut meta: HashMap<Arc<str>, ColumnMeta> = HashMap::new();
316 build_column_meta(all_columns, 0, None, &mut meta);
317
318 let mut visible_cache: HashMap<Arc<str>, bool> = HashMap::new();
319
320 let mut max_depth = 0usize;
321 find_max_depth(
322 all_columns,
323 1,
324 &meta,
325 leaf_visible,
326 &mut visible_cache,
327 &mut max_depth,
328 );
329
330 if max_depth == 0 {
331 return Vec::new();
332 }
333
334 let mut arena: Vec<HeaderNode> = Vec::new();
335 let bottom_headers: Vec<usize> = columns_to_group
336 .iter()
337 .enumerate()
338 .map(|(index, col_id)| {
339 let idx = arena.len();
340 arena.push(HeaderNode {
341 id: col_id.clone(),
342 column_id: col_id.clone(),
343 depth: max_depth,
344 index,
345 is_placeholder: false,
346 placeholder_id: None,
347 col_span: 0,
348 row_span: 0,
349 sub_headers: Vec::new(),
350 });
351 idx
352 })
353 .collect();
354
355 let mut header_groups: Vec<(usize, Arc<str>, Vec<usize>)> = Vec::new();
356 create_header_group(
357 bottom_headers,
358 max_depth.saturating_sub(1),
359 &meta,
360 leaf_visible,
361 &mut visible_cache,
362 header_family,
363 &mut header_groups,
364 &mut arena,
365 );
366
367 header_groups.reverse();
368
369 if let Some((_, _, headers)) = header_groups.first() {
370 recurse_headers_for_spans(
371 headers,
372 &meta,
373 leaf_visible,
374 &mut visible_cache,
375 arena.as_mut_slice(),
376 );
377 }
378
379 header_groups
380 .into_iter()
381 .map(|(depth, id, headers)| HeaderGroupSnapshot {
382 depth,
383 id,
384 headers: snapshot_group(&headers, &arena),
385 })
386 .collect()
387}