use crate::compiler::ast::StmtList;
use crate::error::{DialogueError, Result};
use crate::saliency::Candidate;
use crate::value::VariableStorage;
use super::Runner;
impl<S: VariableStorage> Runner<S> {
pub(super) fn pick_node_body(&mut self, title: &str) -> Result<StmtList> {
let Some(group) = self.program.node_group(title) else {
return Err(DialogueError::UnknownNode(title.to_owned()));
};
let has_when = group.iter().any(|n| n.when.is_some());
if !has_when {
return Ok(StmtList::clone(&group[0].body));
}
let candidate_info: Vec<(String, bool, StmtList)> = group
.iter()
.map(|n| {
let available = n
.when
.as_ref()
.is_none_or(|e| self.eval_expr(e.as_ref()).is_ok_and(|v| v.is_truthy()));
(n.title.clone(), available, StmtList::clone(&n.body))
})
.collect();
let candidate_ids: Vec<String> = candidate_info
.iter()
.enumerate()
.map(|(i, (t, _, _))| format!("{t}#{i}"))
.collect();
let candidates: Vec<Candidate<'_>> = candidate_ids
.iter()
.zip(candidate_info.iter())
.map(|(id, (_, available, _))| Candidate {
id: id.as_str(),
available: *available,
})
.collect();
let idx = self.saliency.select(&candidates).ok_or_else(|| {
DialogueError::ProtocolViolation(format!(
"node group '{title}' has no available candidate"
))
})?;
let len = candidate_info.len();
candidate_info
.into_iter()
.nth(idx)
.map(|(_, _, body)| body)
.ok_or_else(|| {
DialogueError::ProtocolViolation(format!(
"saliency strategy returned index {idx} but node group '{title}' \
only has {len} candidate(s)"
))
})
}
}