use super::{Candidate, LocatedNode, ResolvedContent, is_identity};
use super::{plan::Candidates, preview::LiteralHit};
use mant_ir::{Block, EntryOwner, Section};
use mant_protocol::EvidenceBasis;
use std::collections::HashMap;
pub(super) fn collect<'a>(
content: &'a ResolvedContent,
query: &str,
located: &[LocatedNode<'a>],
_validation: Option<&mant_ir::DocumentValidation<'_>>,
) -> (Candidates<'a>, Vec<usize>, super::support::SupportIndex<'a>) {
let mut scan = Scan {
query,
located,
owners: located
.iter()
.enumerate()
.filter_map(|(i, node)| super::owner(node).map(|o| (owner_key(o), i)))
.collect(),
sections: located
.iter()
.enumerate()
.filter_map(|(i, node)| match node {
LocatedNode::Section { section, .. } => {
Some((std::ptr::from_ref(*section) as usize, i))
}
LocatedNode::Entry { .. } => None,
})
.collect(),
candidates: Candidates::default(),
next_order: 0,
orders: vec![0; located.len()],
supports: super::support::SupportIndex::default(),
};
if let Some(document) = &content.document {
scan.blocks(&document.blocks, None, None, "root");
scan.sections(&document.sections, "sections");
}
(scan.candidates, scan.orders, scan.supports)
}
struct Scan<'a, 'b> {
supports: super::support::SupportIndex<'a>,
query: &'b str,
located: &'b [LocatedNode<'a>],
owners: HashMap<usize, usize>,
sections: HashMap<usize, usize>,
candidates: Candidates<'a>,
next_order: usize,
orders: Vec<usize>,
}
impl<'a> Scan<'a, '_> {
fn sections(&mut self, sections: &'a [Section], path: &str) {
for (ordinal, section) in sections.iter().enumerate() {
let path = format!("{path}/s{ordinal}");
let index = self.sections[&(std::ptr::from_ref(section) as usize)];
self.blocks(§ion.blocks, None, Some(index), &path);
self.sections(§ion.children, &path);
}
}
fn enter_owner(&mut self, owner: EntryOwner<'a>, current: Option<usize>) -> Option<usize> {
let Some(&index) = self.owners.get(&owner_key(owner)) else {
return current;
};
self.orders[index] = self.next_order;
self.next_order += 1;
let LocatedNode::Entry { entry, .. } = &self.located[index] else {
unreachable!("owner location")
};
let (matched, mut bases) =
super::matches::MatchPlan::collect(owner, entry.names(), self.query);
if is_identity(&self.located[index], self.query) {
let mut fields = Vec::new();
if self.located[index].id() == self.query {
fields.push(mant_protocol::ExplanationIdentityField::Id);
}
if self
.query
.parse::<mant_ir::OutlinePath>()
.is_ok_and(|path| &path == self.located[index].path())
{
fields.push(mant_protocol::ExplanationIdentityField::Path);
}
bases.push(EvidenceBasis::Identity { fields });
}
if !bases.is_empty() {
self.add_owner(index, bases, Vec::new(), matched);
}
Some(index)
}
fn add_owner(
&mut self,
index: usize,
bases: Vec<EvidenceBasis>,
hits: Vec<LiteralHit<'a>>,
matched: super::matches::MatchPlan,
) {
self.candidates.insert(Candidate {
order: self.orders[index],
located: Some(index),
ordinary: None,
section: None,
block_path: None,
source: self.located[index].source(),
bases,
matched,
hits,
});
}
fn blocks(
&mut self,
blocks: &'a [Block],
current: Option<usize>,
section: Option<usize>,
path: &str,
) {
for (index, block) in blocks.iter().enumerate() {
let block_path = format!("{path}/b{index}");
let order = self.next_order;
self.next_order += 1;
match block {
Block::List { items, .. } => {
self.supports.record(block, &block_path, &self.owners);
for (i, item) in items.iter().enumerate() {
let owner = self.enter_owner(EntryOwner::List(item), current);
self.blocks(&item.blocks, owner, section, &format!("{block_path}/i{i}"));
}
}
Block::DefinitionList { items, .. } => {
self.supports.record(block, &block_path, &self.owners);
for (i, item) in items.iter().enumerate() {
let owner = self.enter_owner(EntryOwner::Definition(item), current);
self.blocks(
&item.description,
owner,
section,
&format!("{block_path}/d{i}"),
);
}
}
Block::Table { rows, .. } => {
for (r, row) in rows.iter().enumerate() {
for (c, cell) in row.cells.iter().enumerate() {
self.blocks(
&cell.blocks,
current,
section,
&format!("{block_path}/r{r}/c{c}"),
);
}
}
}
_ => {
let Some(text) = super::literal::block_text(block) else {
continue;
};
let Some(range) = super::literal::first_match(&text, self.query) else {
continue;
};
let hit = LiteralHit {
block,
path: block_path.clone(),
range,
};
if let Some(owner) = current {
self.add_owner(
owner,
vec![EvidenceBasis::Literal],
vec![hit],
super::matches::MatchPlan::default(),
);
} else {
self.candidates.insert(Candidate {
order,
located: None,
ordinary: Some(block),
section,
block_path: Some(block_path),
source: mant_ir::geometry::block_source(block),
bases: vec![EvidenceBasis::Literal],
matched: super::matches::MatchPlan::default(),
hits: vec![hit],
});
}
}
}
}
}
}
fn owner_key(owner: EntryOwner<'_>) -> usize {
match owner {
EntryOwner::List(item) => std::ptr::from_ref(item) as usize,
EntryOwner::Definition(item) => std::ptr::from_ref(item) as usize,
}
}