1use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use schemars::JsonSchema;
15use std::time::{SystemTime, UNIX_EPOCH};
16use std::collections::{HashMap, HashSet};
17
18use super::storage::ArborStorage;
19use super::types::{ArborError, NodeId, NodeType, TreeId, Tree};
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct RangeHandle {
28 pub tree_id: TreeId,
30 pub start_node: NodeId,
32 pub end_node: NodeId,
34 pub collapse_type: CollapseType,
36}
37
38impl RangeHandle {
39 pub fn to_metadata(&self) -> Value {
41 serde_json::json!({
42 "range_handle": {
43 "tree_id": self.tree_id,
44 "start_node": self.start_node,
45 "end_node": self.end_node,
46 "collapse_type": self.collapse_type,
47 }
48 })
49 }
50
51 pub fn from_metadata(metadata: &Value) -> Option<Self> {
53 use super::types::ArborId;
54
55 let range_obj = metadata.get("range_handle")?;
56 let tree_id_str = range_obj.get("tree_id")?.as_str()?;
57 let start_node_str = range_obj.get("start_node")?.as_str()?;
58 let end_node_str = range_obj.get("end_node")?.as_str()?;
59
60 let tree_id = ArborId::parse_str(tree_id_str).ok()?;
61 let start_node = ArborId::parse_str(start_node_str).ok()?;
62 let end_node = ArborId::parse_str(end_node_str).ok()?;
63
64 let collapse_type = serde_json::from_value(
65 range_obj.get("collapse_type")?.clone()
66 ).ok()?;
67
68 Some(RangeHandle {
69 tree_id,
70 start_node,
71 end_node,
72 collapse_type,
73 })
74 }
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
79#[serde(tag = "type", rename_all = "snake_case")]
80pub enum CollapseType {
81 TextMerge,
83 StructureRef,
85 Custom { strategy: String },
87}
88
89#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
91#[serde(rename_all = "snake_case")]
92pub enum ResolveMode {
93 Full,
95 Placeholder,
97 Partial { max_depth: usize },
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
103pub struct RangeSpec {
104 pub tree_id: TreeId,
106 pub start_node: NodeId,
108 pub end_node: NodeId,
110 pub collapse_type: CollapseType,
112}
113
114#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
116pub struct TextRun {
117 pub start_node: NodeId,
119 pub end_node: NodeId,
121 pub length: usize,
123 pub char_count: usize,
125}
126
127impl ArborStorage {
132 pub async fn view_create(
134 &self,
135 source_tree_id: &TreeId,
136 owner_id: &str,
137 ) -> Result<TreeId, ArborError> {
138 let now = SystemTime::now()
139 .duration_since(UNIX_EPOCH)
140 .unwrap()
141 .as_secs() as i64;
142
143 let metadata = serde_json::json!({
144 "is_view": true,
145 "source_tree": source_tree_id,
146 "created_at": now,
147 });
148
149 self.tree_create(Some(metadata), owner_id).await
150 }
151
152 pub async fn view_add_range(
154 &self,
155 view_tree_id: &TreeId,
156 parent_node: &NodeId,
157 range_spec: RangeSpec,
158 ) -> Result<NodeId, ArborError> {
159 let range_handle = RangeHandle {
160 tree_id: range_spec.tree_id.clone(),
161 start_node: range_spec.start_node.clone(),
162 end_node: range_spec.end_node.clone(),
163 collapse_type: range_spec.collapse_type.clone(),
164 };
165
166 let range_content = self.range_get(
168 &range_spec.tree_id,
169 &range_spec.start_node,
170 &range_spec.end_node,
171 &range_spec.collapse_type,
172 ).await?;
173
174 use crate::activations::claudecode::NodeEvent;
176 let merged_content = match range_content {
177 RangeContent::Text { content, .. } => {
178 let node_event = NodeEvent::ContentText { text: content };
180 serde_json::to_string(&node_event).unwrap_or_default()
181 }
182 RangeContent::Reference { .. } => {
183 serde_json::to_string(&range_handle).unwrap_or_default()
185 }
186 RangeContent::Custom { metadata, .. } => {
187 metadata.to_string()
189 }
190 };
191
192 let metadata = range_handle.to_metadata();
193
194 self.node_create_text(
196 view_tree_id,
197 Some(*parent_node),
198 merged_content, Some(metadata), )
201 .await
202 }
203
204 fn build_child_map(tree: &Tree) -> HashMap<NodeId, Vec<NodeId>> {
210 let mut children: HashMap<NodeId, Vec<NodeId>> = HashMap::new();
211
212 for (node_id, node) in &tree.nodes {
213 if let Some(parent_id) = &node.parent {
214 children.entry(*parent_id)
215 .or_insert_with(Vec::new)
216 .push(*node_id);
217 }
218 }
219
220 children
221 }
222
223 fn traverse_dfs_from(
225 current_id: &NodeId,
226 children: &HashMap<NodeId, Vec<NodeId>>,
227 visited: &mut Vec<NodeId>
228 ) {
229 visited.push(*current_id);
230
231 if let Some(child_ids) = children.get(current_id) {
232 for child_id in child_ids {
233 Self::traverse_dfs_from(child_id, children, visited);
234 }
235 }
236 }
237
238 fn traverse_tree_dfs(tree: &Tree) -> Vec<NodeId> {
240 let root_id = tree.nodes.iter()
242 .find(|(_, node)| node.parent.is_none())
243 .map(|(id, _)| *id);
244
245 let Some(root_id) = root_id else {
246 return Vec::new();
248 };
249
250 let children = Self::build_child_map(tree);
251 let mut visited = Vec::new();
252 Self::traverse_dfs_from(&root_id, &children, &mut visited);
253 visited
254 }
255
256 pub async fn view_detect_text_runs(
262 &self,
263 tree_id: &TreeId,
264 min_length: usize,
265 ) -> Result<Vec<TextRun>, ArborError> {
266 let tree = self.tree_get(tree_id).await?;
267
268 let mut runs = Vec::new();
269 let mut current_run: Option<(NodeId, NodeId, usize, usize)> = None; let is_text_node = |node_id: &NodeId| -> Option<usize> {
273 let node = tree.nodes.get(node_id)?;
274
275 match &node.data {
276 NodeType::Text { content } => {
277 if let Ok(parsed) = serde_json::from_str::<Value>(content) {
279 if parsed.get("type")?.as_str()? == "content_text" {
280 let text = parsed.get("text")?.as_str()?;
281 return Some(text.len());
282 }
283 }
284
285 if content.is_empty() {
288 None
289 } else {
290 Some(content.len())
291 }
292 }
293 NodeType::External { .. } => None,
294 }
295 };
296
297 let node_ids = Self::traverse_tree_dfs(&tree);
299
300 for node_id in &node_ids {
301 if let Some(char_count) = is_text_node(node_id) {
302 match &mut current_run {
303 Some((start, end, count, chars)) => {
304 let node = tree.nodes.get(node_id).unwrap();
306 if node.parent.as_ref() == Some(end) {
307 *end = node_id.clone();
309 *count += 1;
310 *chars += char_count;
311 } else {
312 if *count >= min_length {
314 runs.push(TextRun {
315 start_node: start.clone(),
316 end_node: end.clone(),
317 length: *count,
318 char_count: *chars,
319 });
320 }
321 current_run = Some((node_id.clone(), node_id.clone(), 1, char_count));
322 }
323 }
324 None => {
325 current_run = Some((node_id.clone(), node_id.clone(), 1, char_count));
327 }
328 }
329 } else {
330 if let Some((start, end, count, chars)) = current_run.take() {
332 if count >= min_length {
333 runs.push(TextRun {
334 start_node: start,
335 end_node: end,
336 length: count,
337 char_count: chars,
338 });
339 }
340 }
341 }
342 }
343
344 if let Some((start, end, count, chars)) = current_run {
346 if count >= min_length {
347 runs.push(TextRun {
348 start_node: start,
349 end_node: end,
350 length: count,
351 char_count: chars,
352 });
353 }
354 }
355
356 Ok(runs)
357 }
358
359 pub async fn view_collapse_text_runs(
361 &self,
362 source_tree_id: &TreeId,
363 min_run_length: usize,
364 owner_id: &str,
365 ) -> Result<(TreeId, Vec<TextRun>), ArborError> {
366 let runs = self.view_detect_text_runs(source_tree_id, min_run_length).await?;
368
369 let view_tree_id = self.view_create(source_tree_id, owner_id).await?;
371
372 let source_tree = self.tree_get(source_tree_id).await?;
374
375 let children = Self::build_child_map(&source_tree);
377
378 let mut collapsed_nodes: HashSet<NodeId> = HashSet::new();
382 let mut run_starts: HashMap<NodeId, &TextRun> = HashMap::new();
383
384 for run in &runs {
385 let mut current = run.start_node;
387 run_starts.insert(current, run);
388 collapsed_nodes.insert(current);
389
390 while current != run.end_node {
392 if let Some(child_ids) = children.get(¤t) {
393 if let Some(&child_id) = child_ids.first() {
395 current = child_id;
396 collapsed_nodes.insert(current);
397 } else {
398 break;
399 }
400 } else {
401 break;
402 }
403 }
404 }
405
406 let mut node_mapping: HashMap<NodeId, NodeId> = HashMap::new();
409 let view_tree = self.tree_get(&view_tree_id).await?;
410
411 if let Some(source_root) = source_tree.nodes.iter()
413 .find(|(_, node)| node.parent.is_none())
414 .map(|(id, _)| *id)
415 {
416 node_mapping.insert(source_root, view_tree.root);
417 }
418
419 let traversal_order = Self::traverse_tree_dfs(&source_tree);
420
421 for source_node_id in traversal_order {
422 if collapsed_nodes.contains(&source_node_id) && !run_starts.contains_key(&source_node_id) {
424 continue;
425 }
426
427 let source_node = source_tree.nodes.get(&source_node_id).unwrap();
428
429 let view_parent = source_node.parent.as_ref()
431 .and_then(|source_parent| node_mapping.get(source_parent).copied());
432
433 if let Some(run) = run_starts.get(&source_node_id) {
435 let range_spec = RangeSpec {
436 tree_id: *source_tree_id,
437 start_node: run.start_node,
438 end_node: run.end_node,
439 collapse_type: CollapseType::TextMerge,
440 };
441
442 if let Ok(view_node_id) = self.view_add_range(&view_tree_id, &view_parent.unwrap_or(view_tree.root), range_spec).await {
443 node_mapping.insert(source_node_id, view_node_id);
445 }
446 } else {
447 let view_node_id = match &source_node.data {
449 NodeType::Text { content } => {
450 self.node_create_text(
451 &view_tree_id,
452 view_parent,
453 content.clone(),
454 source_node.metadata.clone(),
455 ).await.ok()
456 }
457 NodeType::External { handle } => {
458 self.node_create_external(
459 &view_tree_id,
460 view_parent,
461 handle.clone(),
462 source_node.metadata.clone(),
463 ).await.ok()
464 }
465 };
466
467 if let Some(view_node_id) = view_node_id {
468 node_mapping.insert(source_node_id, view_node_id);
469 }
470 }
471 }
472
473 Ok((view_tree_id, runs))
474 }
475
476 pub async fn range_get(
478 &self,
479 tree_id: &TreeId,
480 start_node: &NodeId,
481 end_node: &NodeId,
482 collapse_type: &CollapseType,
483 ) -> Result<RangeContent, ArborError> {
484 let tree = self.tree_get(tree_id).await?;
485
486 let mut path_nodes = Vec::new();
488 let mut current = end_node.clone();
489
490 while current != *start_node {
492 path_nodes.push(current);
493
494 let node = tree.nodes.get(¤t)
495 .ok_or_else(|| ArborError {
496 message: format!("Node not found: {}", current)
497 })?;
498
499 if let Some(parent) = &node.parent {
500 current = *parent;
501 } else {
502 return Err(ArborError {
503 message: "end_node is not a descendant of start_node".to_string()
504 });
505 }
506 }
507
508 path_nodes.push(start_node.clone());
509 path_nodes.reverse();
510
511 match collapse_type {
512 CollapseType::TextMerge => {
513 let mut merged_text = String::new();
515
516 for node_id in &path_nodes {
517 if let Some(node) = tree.nodes.get(node_id) {
518 if let NodeType::Text { content } = &node.data {
519 if let Ok(parsed) = serde_json::from_str::<Value>(content) {
521 if parsed.get("type").and_then(|t| t.as_str()) == Some("content_text") {
522 if let Some(text) = parsed.get("text").and_then(|t| t.as_str()) {
523 merged_text.push_str(text);
524 }
525 continue;
526 }
527 }
528
529 if !content.is_empty() {
531 merged_text.push_str(content);
532 }
533 }
534 }
535 }
536
537 Ok(RangeContent::Text {
538 content: merged_text,
539 node_count: path_nodes.len(),
540 node_ids: path_nodes,
541 })
542 }
543 CollapseType::StructureRef => {
544 Ok(RangeContent::Reference {
545 tree_id: tree_id.clone(),
546 start_node: start_node.clone(),
547 end_node: end_node.clone(),
548 node_count: path_nodes.len(),
549 })
550 }
551 CollapseType::Custom { strategy } => {
552 Ok(RangeContent::Custom {
553 strategy: strategy.clone(),
554 node_count: path_nodes.len(),
555 metadata: serde_json::json!({
556 "tree_id": tree_id,
557 "start": start_node,
558 "end": end_node,
559 }),
560 })
561 }
562 }
563 }
564}
565
566#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
568#[serde(tag = "type", rename_all = "snake_case")]
569pub enum RangeContent {
570 Text {
572 content: String,
573 node_count: usize,
574 node_ids: Vec<NodeId>,
575 },
576 Reference {
578 tree_id: TreeId,
579 start_node: NodeId,
580 end_node: NodeId,
581 node_count: usize,
582 },
583 Custom {
585 strategy: String,
586 node_count: usize,
587 metadata: Value,
588 },
589}