use std::collections::VecDeque;
use std::sync::Arc;
use surrealdb_types::ToSql;
use super::common::{RecursionBounds, eval_buffered, is_recursion_target};
use crate::exec::FlowResult;
use crate::exec::parts::recurse::value_hash;
use crate::exec::parts::{evaluate_physical_path, is_final};
use crate::exec::physical_expr::{EvalContext, PhysicalExpr};
use crate::val::Value;
pub(crate) async fn evaluate_recurse_shortest(
start: &Value,
target: &Value,
path: &[Arc<dyn PhysicalExpr>],
bounds: RecursionBounds,
inclusive: bool,
ctx: EvalContext<'_>,
) -> FlowResult<Value> {
let min_depth = bounds.min;
let max_depth = bounds.cap();
let mut seen = std::collections::HashSet::new();
let initial_path = if inclusive {
vec![start.clone()]
} else {
vec![]
};
let mut queue: VecDeque<(Value, Vec<Value>)> = VecDeque::new();
queue.push_back((start.clone(), initial_path));
seen.insert(value_hash(start));
let mut depth = 0u32;
while depth < max_depth && !queue.is_empty() {
let level: Vec<(Value, Vec<Value>)> = queue.drain(..).collect();
let futures: Vec<_> = level
.iter()
.map(|(current, _)| evaluate_physical_path(current, path, ctx.with_value(current)))
.collect();
let eval_results = eval_buffered(futures).await?;
for ((_, current_path), result) in level.into_iter().zip(eval_results) {
let values = match result {
Value::Array(arr) => arr.0,
Value::None | Value::Null => continue,
other => vec![other],
};
for v in values {
if is_final(&v) {
continue;
}
if !is_recursion_target(&v) {
return Err(crate::err::Error::InvalidRecursionTarget {
value: v.to_sql(),
}
.into());
}
if depth + 1 >= min_depth && &v == target {
let mut final_path = current_path;
final_path.push(v);
return Ok(Value::Array(final_path.into()));
}
let hash = value_hash(&v);
if seen.insert(hash) {
let mut new_path = current_path.clone();
new_path.push(v.clone());
queue.push_back((v, new_path));
}
}
}
depth += 1;
}
if bounds.errors_on_limit() && !queue.is_empty() {
return Err(crate::err::Error::IdiomRecursionLimitExceeded {
limit: bounds.system_limit,
}
.into());
}
let remaining_paths: Vec<Value> = queue
.into_iter()
.filter(|(_, p)| !p.is_empty())
.map(|(_, p)| Value::Array(p.into()))
.collect();
if remaining_paths.is_empty() {
Ok(Value::None)
} else {
Ok(Value::Array(remaining_paths.into()))
}
}