use std::io;
use rig_core::conversation::{Conversation, MessageId};
use crate::SessionError;
use crate::format::{Body, LogLines, parse};
use crate::session::read_header;
fn at_rest(conversation: &Conversation) -> bool {
let ended = conversation.turns().next_back();
!conversation.awaits_model()
&& conversation.pending_calls().is_empty()
&& ended.is_none_or(|(_, turn)| turn.end.is_some())
}
pub(crate) fn turn_cut(
name: &str,
lines: &LogLines,
through: Option<MessageId>,
) -> Result<u64, SessionError> {
let mut records = lines.iter();
let Some((_, mut cut, first)) = records.next() else {
return Ok(0);
};
read_header(name, first)?;
let refuse = |line, why| SessionError::Record {
log: name.to_owned(),
line,
why,
};
let mut conversation = Conversation::new();
let mut held = false;
for (line, end, bytes) in records {
if let (_, Body::Change(recorded)) = parse(bytes).map_err(|why| refuse(line, why))? {
conversation
.apply(&recorded)
.map_err(|error| refuse(line, error.to_string()))?;
}
if let Some(through) = through {
let last = conversation.entries().next_back().map(|entry| entry.id);
if last != Some(through) && (held || conversation.index_of(through).is_some()) {
break;
}
held |= last == Some(through);
}
if at_rest(&conversation) {
cut = end;
}
}
match through {
Some(through) if !held && conversation.index_of(through).is_none() => {
let why = format!("the log `{name}` holds no message {through:?}");
Err(io::Error::new(io::ErrorKind::NotFound, why).into())
}
_ => Ok(cut),
}
}
#[cfg(test)]
mod tests;