use super::*;
#[derive(Debug, Clone, Copy)]
pub(crate) struct Slot {
pub(crate) first: usize,
pub(crate) last: usize,
}
pub(crate) struct Builder<'a> {
pub(crate) recs: Vec<&'a Record>,
pub(crate) admit: Vec<bool>,
pub(crate) parent: Vec<Option<&'a str>>,
pub(crate) map: HashMap<&'a str, Slot>,
pub(crate) removed: Vec<bool>,
pub(crate) on_chain: Vec<bool>,
pub(crate) precut: Vec<bool>,
pub(crate) visited: Vec<bool>,
pub(crate) replay_of: HashMap<usize, usize>,
pub(crate) chain_child: HashMap<&'a str, usize>,
pub(crate) ts_sorted: Option<Vec<(i64, usize)>>,
pub(crate) opens: Vec<bool>,
pub(crate) child_off: Vec<usize>,
pub(crate) child_at: Vec<usize>,
}
pub(crate) fn chain_admits(r: &Record) -> bool {
if r.is_elicitation_marker() {
return false;
}
if r.uuid.as_deref().is_none_or(str::is_empty) {
return false;
}
matches!(
r.r#type.as_deref(),
Some("user" | "assistant" | "attachment" | "system")
)
}
impl<'a> Builder<'a> {
pub(crate) fn new<T>(records: &'a [T], rec: &impl Fn(&T) -> &Record) -> Builder<'a> {
let recs: Vec<&'a Record> = records.iter().map(rec).collect();
let n = recs.len();
let mut admit = Vec::with_capacity(n);
let mut parent: Vec<Option<&'a str>> = Vec::with_capacity(n);
let mut map: HashMap<&'a str, Slot> = HashMap::with_capacity(n);
let mut replay_of: HashMap<usize, usize> = HashMap::new();
for (i, r) in recs.iter().copied().enumerate() {
let ok = chain_admits(r);
admit.push(ok);
parent.push(r.parent_uuid.as_deref().filter(|s| !s.is_empty()));
if !ok {
continue;
}
let Some(uuid) = r.uuid.as_deref() else {
continue;
};
match map.get_mut(uuid) {
Some(slot) => {
replay_of.insert(slot.last, i);
slot.last = i;
}
None => {
map.insert(uuid, Slot { first: i, last: i });
}
}
}
for (i, target) in &mut replay_of {
if let Some(last) = recs[*i].uuid.as_deref().and_then(|u| map.get(u)) {
*target = last.last;
}
}
let opens = recs
.iter()
.map(|r| r.r#type.as_deref() == Some("user") && r.opens_turn())
.collect();
Builder {
recs,
admit,
parent,
map,
removed: vec![false; n],
on_chain: vec![false; n],
precut: vec![false; n],
visited: vec![false; n],
replay_of,
chain_child: HashMap::new(),
ts_sorted: None,
opens,
child_off: Vec::new(),
child_at: Vec::new(),
}
}
pub(crate) fn index_children(&mut self) {
let n = self.len();
let mut parent_of: Vec<Option<usize>> = vec![None; n];
let mut counts = vec![0usize; n + 1];
for (i, slot) in parent_of.iter_mut().enumerate() {
if !self.admit[i] || !self.is_survivor(i) {
continue;
}
if let Some(p) = self.parent[i].and_then(|p| self.resolve(p)) {
*slot = Some(p);
counts[p] += 1;
}
}
let mut off = vec![0usize; n + 1];
let mut acc = 0usize;
for i in 0..n {
off[i] = acc;
acc += counts[i];
}
off[n] = acc;
let mut cursor = off.clone();
let mut at = vec![0usize; acc];
for (i, p) in parent_of.iter().enumerate() {
if let Some(p) = *p {
at[cursor[p]] = i;
cursor[p] += 1;
}
}
self.child_off = off;
self.child_at = at;
}
pub(crate) fn children_of(&self, i: usize) -> &[usize] {
match (self.child_off.get(i), self.child_off.get(i + 1)) {
(Some(&a), Some(&b)) => &self.child_at[a..b],
_ => &[],
}
}
pub(crate) fn len(&self) -> usize {
self.recs.len()
}
pub(crate) fn resolve(&self, uuid: &str) -> Option<usize> {
let slot = self.map.get(uuid)?;
(!self.removed[slot.last]).then_some(slot.last)
}
pub(crate) fn is_survivor(&self, i: usize) -> bool {
self.admit[i] && !self.replay_of.contains_key(&i)
}
pub(crate) fn uuid(&self, i: usize) -> Option<&'a str> {
self.recs[i].uuid.as_deref()
}
pub(crate) fn is_conv(&self, i: usize) -> bool {
matches!(self.recs[i].r#type.as_deref(), Some("user" | "assistant"))
}
pub(crate) fn is_boundary(&self, i: usize) -> bool {
self.recs[i].r#type.as_deref() == Some("system")
&& self.recs[i].subtype.as_deref() == Some("compact_boundary")
}
pub(crate) fn is_sidechain(&self, i: usize) -> bool {
self.recs[i].is_sidechain == Some(true)
}
}