use std::collections::{HashMap, HashSet, VecDeque};
use super::InMemory;
use crate::{
Result,
backend::errors::BackendError,
entry::{Entry, ID},
};
pub(crate) fn calculate_heights(
backend: &InMemory,
tree: &ID,
subtree: Option<&str>,
) -> Result<HashMap<ID, usize>> {
match subtree {
None => {
let heights_cache = backend.heights.read().unwrap();
if let Some(tree_cache) = heights_cache.get(tree) {
let entries = backend.entries.read().unwrap();
let mut tree_entries = Vec::new();
for (id, entry) in entries.iter() {
if entry.in_tree(tree) {
tree_entries.push(id.clone());
}
}
drop(entries);
let mut result = HashMap::new();
let mut all_cached = true;
for id in tree_entries {
if let Some((height, _)) = tree_cache.get(&id) {
result.insert(id, *height);
} else {
all_cached = false;
break;
}
}
if all_cached {
return Ok(result);
}
}
drop(heights_cache);
let computed_heights = calculate_heights_original(backend, tree, None)?;
let mut heights_cache = backend.heights.write().unwrap();
let tree_cache = heights_cache.entry(tree.clone()).or_default();
for (id, height) in &computed_heights {
tree_cache
.entry(id.clone())
.and_modify(|(th, _)| *th = *height)
.or_insert((*height, HashMap::new()));
}
Ok(computed_heights)
}
Some(subtree_name) => {
let heights_cache = backend.heights.read().unwrap();
if let Some(tree_cache) = heights_cache.get(tree) {
let entries = backend.entries.read().unwrap();
let mut subtree_entries = Vec::new();
for (id, entry) in entries.iter() {
if entry.in_tree(tree) && entry.in_subtree(subtree_name) {
subtree_entries.push(id.clone());
}
}
drop(entries);
let mut result = HashMap::new();
let mut all_cached = true;
for id in subtree_entries {
if let Some((_, subtree_heights)) = tree_cache.get(&id) {
if let Some(height) = subtree_heights.get(subtree_name) {
result.insert(id, *height);
} else {
all_cached = false;
break;
}
} else {
all_cached = false;
break;
}
}
if all_cached {
return Ok(result);
}
}
drop(heights_cache);
let computed_heights = calculate_heights_original(backend, tree, Some(subtree_name))?;
let mut heights_cache = backend.heights.write().unwrap();
let tree_cache = heights_cache.entry(tree.clone()).or_default();
for (id, height) in &computed_heights {
tree_cache
.entry(id.clone())
.and_modify(|(_, sh)| {
sh.insert(subtree_name.to_string(), *height);
})
.or_insert((0, [(subtree_name.to_string(), *height)].into()));
}
Ok(computed_heights)
}
}
}
fn calculate_heights_original(
backend: &InMemory,
tree: &ID,
subtree: Option<&str>,
) -> Result<HashMap<ID, usize>> {
let mut heights: HashMap<ID, usize> = HashMap::new();
let mut in_degree: HashMap<ID, usize> = HashMap::new();
let mut children_map: HashMap<ID, Vec<ID>> = HashMap::new();
let mut nodes_in_context: HashSet<ID> = HashSet::new();
let entries = backend.entries.read().unwrap();
for (id, entry) in entries.iter() {
let in_context = match subtree {
Some(subtree_name) => entry.in_tree(tree) && entry.in_subtree(subtree_name),
None => entry.in_tree(tree),
};
if !in_context {
continue;
}
nodes_in_context.insert(id.clone());
let parents = match subtree {
Some(subtree_name) => entry.subtree_parents(subtree_name)?,
None => entry.parents()?,
};
in_degree.insert(id.clone(), parents.len());
for parent_id in parents {
let parent_in_context = entries
.get(&parent_id)
.is_some_and(|p_entry| match subtree {
Some(subtree_name) => p_entry.in_tree(tree) && p_entry.in_subtree(subtree_name),
None => p_entry.in_tree(tree),
});
if parent_in_context {
children_map
.entry(parent_id.clone())
.or_default()
.push(id.clone());
} else {
if let Some(d) = in_degree.get_mut(id) {
*d = d.saturating_sub(1);
}
}
}
}
let mut queue: VecDeque<ID> = VecDeque::new();
for id in &nodes_in_context {
heights.insert(id.clone(), 0);
let degree = in_degree.get(id).cloned().unwrap_or(0); if degree == 0 {
queue.push_back(id.clone());
}
}
let mut processed_nodes_count = 0;
while let Some(current_id) = queue.pop_front() {
processed_nodes_count += 1;
let current_height =
*heights
.get(¤t_id)
.ok_or_else(|| BackendError::HeightCalculationCorruption {
reason: format!("Height missing for node {current_id}"),
})?;
if let Some(children) = children_map.get(¤t_id) {
for child_id in children {
if !nodes_in_context.contains(child_id) {
continue;
}
let new_height = current_height + 1;
let child_current_height = heights.entry(child_id.clone()).or_insert(0); *child_current_height = (*child_current_height).max(new_height);
if let Some(degree) = in_degree.get_mut(child_id) {
if *degree > 0 {
*degree -= 1;
if *degree == 0 {
queue.push_back(child_id.clone());
}
} else {
return Err(BackendError::HeightCalculationCorruption {
reason: format!("Negative in-degree detected for child {child_id}"),
}
.into());
}
} else {
return Err(BackendError::HeightCalculationCorruption {
reason: format!("In-degree missing for child {child_id}"),
}
.into());
}
}
}
}
if processed_nodes_count != nodes_in_context.len() {
panic!(
"calculate_heights processed {} nodes, but found {} nodes in context. Potential cycle or disconnected graph portion detected.",
processed_nodes_count,
nodes_in_context.len()
);
}
heights.retain(|id, _| nodes_in_context.contains(id));
Ok(heights)
}
pub(crate) fn sort_entries_by_height(
backend: &InMemory,
tree: &ID,
entries: &mut [Entry],
) -> Result<()> {
let heights = calculate_heights(backend, tree, None)?;
entries.sort_by(|a, b| {
let a_height = *heights.get(&a.id()).unwrap_or(&0);
let b_height = *heights.get(&b.id()).unwrap_or(&0);
a_height.cmp(&b_height).then_with(|| a.id().cmp(&b.id()))
});
Ok(())
}
pub(crate) fn sort_entries_by_subtree_height(
backend: &InMemory,
tree: &ID,
subtree: &str,
entries: &mut [Entry],
) -> Result<()> {
let heights = calculate_heights(backend, tree, Some(subtree))?;
entries.sort_by(|a, b| {
let a_height = *heights.get(&a.id()).unwrap_or(&0);
let b_height = *heights.get(&b.id()).unwrap_or(&0);
a_height.cmp(&b_height).then_with(|| a.id().cmp(&b.id()))
});
Ok(())
}
pub(crate) fn create_crdt_cache_key(entry_id: &ID, subtree: &str) -> String {
let mut key = String::with_capacity(5 + entry_id.as_str().len() + 1 + subtree.len());
key.push_str("crdt:");
key.push_str(entry_id.as_str());
key.push(':');
key.push_str(subtree);
key
}
pub(crate) fn get_cached_crdt_state(
backend: &InMemory,
entry_id: &ID,
subtree: &str,
) -> Result<Option<String>> {
let key = create_crdt_cache_key(entry_id, subtree);
let cache = backend.cache.read().unwrap();
Ok(cache.get(&key).cloned())
}
pub(crate) fn cache_crdt_state(
backend: &InMemory,
entry_id: &ID,
subtree: &str,
state: String,
) -> Result<()> {
let key = create_crdt_cache_key(entry_id, subtree);
let mut cache = backend.cache.write().unwrap();
cache.insert(key, state);
Ok(())
}
pub(crate) fn clear_crdt_cache(backend: &InMemory) -> Result<()> {
let mut cache = backend.cache.write().unwrap();
cache.clear();
Ok(())
}