use super::*;
struct Recorded<'a> {
leaf: Option<&'a str>,
explicit: bool,
cleared: bool,
}
pub(crate) fn pick<'a>(
b: &Builder<'a>,
hint: Option<&'a str>,
relink_anchor: Option<&'a str>,
) -> (Option<usize>, LeafSource) {
let rec = match hint {
Some(h) => Recorded {
leaf: Some(h),
explicit: false,
cleared: false,
},
None => recorded(b),
};
let tail = tail_index(b);
if rec.cleared {
return (newest(b), LeafSource::Cleared);
}
let resolved = rec.leaf.and_then(|u| b.resolve(u));
let source = match (rec.leaf, resolved, rec.explicit) {
(Some(_), Some(_), true) => LeafSource::Explicit,
(Some(_), Some(_), false) => LeafSource::LastPrompt,
(Some(_), None, _) => LeafSource::TailLeafAbsent,
(None, _, _) => LeafSource::Tail,
};
let use_recorded = relink_anchor.is_none() || (rec.explicit && resolved.is_some());
if use_recorded {
let mut no = resolved;
if let (Some(cur), Some(t)) = (no, tail) {
if !rec.explicit && t != cur && is_ancestor_of(b, cur, t) {
no = Some(t);
}
}
if relink_anchor.is_none() {
no = no.or(tail);
}
if let Some(i) = no.and_then(|i| walk_up_to_conv(b, i)) {
return (Some(i), source);
}
}
(tips(b, resolved, tail), source)
}
fn recorded<'a>(b: &Builder<'a>) -> Recorded<'a> {
let mut out = Recorded {
leaf: None,
explicit: false,
cleared: false,
};
for i in 0..b.len() {
let r = b.recs[i];
if b.is_boundary(i) {
out.leaf = None;
out.explicit = false;
continue;
}
if r.r#type.as_deref() != Some("last-prompt") {
continue;
}
match r.leaf_uuid.as_deref() {
Some(u) if !u.is_empty() => {
out.explicit = r.explicit == Some(true) || (out.explicit && Some(u) == out.leaf);
out.leaf = Some(u);
out.cleared = false;
}
_ if r.explicit == Some(true) => {
out.cleared = true;
out.leaf = None;
out.explicit = false;
}
_ => {}
}
}
out
}
fn tail_index(b: &Builder<'_>) -> Option<usize> {
(0..b.len())
.rev()
.find(|&i| b.admit[i] && !b.removed[i] && b.is_survivor(i) && !b.is_sidechain(i))
}
fn is_ancestor_of(b: &Builder<'_>, anc: usize, start: usize) -> bool {
let mut cur = Some(start);
for _ in 0..=b.len() {
let Some(i) = cur else { return false };
if i == anc {
return true;
}
cur = b.parent[i].and_then(|p| b.resolve(p));
}
false
}
fn walk_up_to_conv(b: &Builder<'_>, start: usize) -> Option<usize> {
let mut cur = Some(start);
for _ in 0..=b.len() {
let i = cur?;
if b.is_conv(i) {
return Some(i);
}
cur = b.parent[i].and_then(|p| b.resolve(p));
}
None
}
fn tips(b: &Builder<'_>, recorded: Option<usize>, tail: Option<usize>) -> Option<usize> {
let mut referenced = vec![false; b.len()];
let mut conv_referenced = vec![false; b.len()];
for i in 0..b.len() {
if !b.admit[i] || b.removed[i] {
continue;
}
if let Some(p) = b.parent[i].and_then(|p| b.resolve(p)) {
referenced[p] = true;
if b.is_conv(i) {
conv_referenced[p] = true;
}
}
}
let mut candidates: Vec<usize> = Vec::new();
for (i, &is_referenced) in referenced.iter().enumerate() {
if !b.admit[i] || b.removed[i] || !b.is_survivor(i) || is_referenced {
continue;
}
let Some(c) = walk_up_to_conv(b, i) else {
continue;
};
if !conv_referenced[c] && !candidates.contains(&c) {
candidates.push(c);
}
}
match candidates.len() {
1 => return candidates.first().copied(),
0 => {}
_ => {
let pick = recorded
.filter(|i| candidates.contains(i))
.or(tail)
.or_else(|| candidates.first().copied());
return pick.and_then(|i| walk_up_to_conv(b, i));
}
}
newest(b).or_else(|| tail.and_then(|i| walk_up_to_conv(b, i)))
}
fn newest(b: &Builder<'_>) -> Option<usize> {
let mut best: Option<(i64, usize)> = None;
for i in 0..b.len() {
if !b.admit[i] || b.removed[i] || !b.is_survivor(i) || b.is_sidechain(i) {
continue;
}
let Some(ms) = b.recs[i]
.timestamp
.as_deref()
.and_then(crate::timez::epoch_ms)
else {
continue;
};
if best.is_none_or(|(bm, _)| ms > bm) {
best = Some((ms, i));
}
}
best.and_then(|(_, i)| walk_up_to_conv(b, i))
}