use crate::db::GraphDb;
use crate::repograph::facts::neighbors;
use crate::Direction;
use core_storage::fs::Fs;
use std::collections::{BTreeMap, BTreeSet, VecDeque};
pub const PATH_EDGES: [&str; 4] = ["IMPORTS", "CALLS", "CO_CHANGED", "MENTIONS"];
pub const MAX_HOPS: usize = 6;
#[must_use]
pub fn shortest_path<F: Fs>(
db: &GraphDb<F>,
a: &str,
b: &str,
edge_types: &[&str],
max_hops: usize,
) -> Vec<(String, String)> {
if a == b || max_hops == 0 || !db.has_node(a) || !db.has_node(b) {
return Vec::new();
}
let mut came_from: BTreeMap<String, (String, String)> = BTreeMap::new();
let mut seen: BTreeSet<String> = [a.to_string()].into_iter().collect();
let mut queue: VecDeque<(String, usize)> = [(a.to_string(), 0)].into_iter().collect();
while let Some((node, depth)) = queue.pop_front() {
if depth == max_hops {
continue;
}
for (next, etype) in step(db, &node, edge_types) {
if !seen.insert(next.clone()) {
continue;
}
came_from.insert(next.clone(), (etype, node.clone()));
if next == b {
return unwind(&came_from, a, b);
}
queue.push_back((next, depth + 1));
}
}
Vec::new()
}
fn step<F: Fs>(db: &GraphDb<F>, node: &str, edge_types: &[&str]) -> Vec<(String, String)> {
let mut out: Vec<(String, String)> = Vec::new();
for etype in edge_types {
for dir in [Direction::Out, Direction::In] {
for nbr in neighbors(db, node, etype, dir) {
out.push((nbr, (*etype).to_string()));
}
}
}
out.sort();
out.dedup_by(|x, y| x.0 == y.0);
out
}
fn unwind(
came_from: &BTreeMap<String, (String, String)>,
a: &str,
b: &str,
) -> Vec<(String, String)> {
let mut hops: Vec<(String, String)> = Vec::new();
let mut node = b.to_string();
while node != a {
let Some((etype, prev)) = came_from.get(&node) else {
return Vec::new(); };
hops.push((etype.clone(), node.clone()));
node = prev.clone();
}
hops.reverse();
hops
}