use polyc_eventlog::Event;
use polyc_state::revision::PartitionIncarnation;
use super::IndexedMessage;
use super::store::Coverage;
use super::terms::TermKey;
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct Projected {
pub(crate) messages: Vec<IndexedMessage>,
pub(crate) coverage: Coverage,
pub(crate) excision_in_range: bool,
pub(crate) open_turn_at: Option<u64>,
}
pub(crate) fn project(
term_key: &TermKey,
partition: &str,
source_incarnation: PartitionIncarnation,
events: &[(u64, Event)],
end: u64,
) -> Projected {
let mut range = events.to_vec();
let excisions = polyc_facts::verified_excisions_matching(&range, partition, |excision| {
format!("conv-{}", excision.conversation_id) == partition
});
let excision_in_range = !excisions.is_empty();
let excised = polyc_facts::excised_positions(&range, &excisions);
polyc_facts::strip_excised(&mut range, &excised);
let withheld = polyc_facts::withheld_turn_ids_positioned(&range);
polyc_facts::withhold_paused_turn_text_positioned(&mut range, &withheld);
let barrier = open_turn_barrier(&range);
let effective_end = barrier.map_or(end, |barrier| end.min(barrier));
let open_turn_at = barrier.filter(|barrier| *barrier < end);
let (indexed, bare): (Vec<u64>, Vec<Event>) = range
.iter()
.filter(|(position, _)| *position < effective_end)
.map(|(position, event)| (*position, event.clone()))
.unzip();
let facts = polyc_facts::committed_message_facts(&bare);
let mut messages = Vec::with_capacity(facts.len());
for fact in &facts {
let Some(&position) = usize::try_from(fact.ordinal)
.ok()
.and_then(|index| indexed.get(index))
else {
continue;
};
messages.push(IndexedMessage {
position,
turn_id: fact.turn_id.clone(),
term_hashes: term_key.hash_text(&fact.text),
});
}
messages.sort_by_key(|message| message.position);
Projected {
coverage: Coverage {
indexed_through: effective_end,
source_incarnation,
available: true,
excision_scanned_through: end,
},
messages,
excision_in_range,
open_turn_at,
}
}
fn open_turn_barrier(range: &[(u64, Event)]) -> Option<u64> {
let mut starts: std::collections::HashMap<uuid::Uuid, u64> = std::collections::HashMap::new();
let mut completed: std::collections::HashSet<uuid::Uuid> = std::collections::HashSet::new();
for (position, event) in range {
let (base, turn_id) = polyc_proto::kinds::parse(&event.kind);
let Some(turn_id) = turn_id else { continue };
if base == polyc_proto::kinds::TURN_START {
starts.entry(turn_id).or_insert(*position);
} else if base == polyc_proto::kinds::TURN_COMPLETE {
completed.insert(turn_id);
}
}
starts
.into_iter()
.filter(|(turn_id, _)| !completed.contains(turn_id))
.map(|(_, position)| position)
.min()
}
#[cfg(test)]
mod tests;