use stellar_xdr::curr::ScSpecEntry;
use crate::ir::{Expr, Literal, MethodCall, Statement};
pub(super) fn recognize_event_structs(
stmts: Vec<Statement>,
all_entries: &[ScSpecEntry],
) -> Vec<Statement> {
let events: Vec<_> = all_entries.iter().filter_map(|e| {
if let ScSpecEntry::EventV0(ev) = e {
Some(ev)
} else { None }
}).collect();
if events.is_empty() { return stmts; }
let mut result = Vec::with_capacity(stmts.len());
let mut i = 0;
while i < stmts.len() {
if i + 1 < stmts.len() {
if let (Some(topics_var), Some(data_expr)) = (
try_extract_vec_topics(&stmts[i]),
try_extract_event_publish(&stmts[i + 1]),
) {
if data_expr.0 == topics_var.0 {
if let Some(event_stmt) = match_event_spec(&events, &topics_var.1, &data_expr.1) {
result.push(event_stmt);
i += 2; continue;
}
}
}
}
result.push(match stmts[i].clone() {
Statement::If { condition, then_body, else_body } => Statement::If {
condition,
then_body: recognize_event_structs(then_body, all_entries),
else_body: recognize_event_structs(else_body, all_entries),
},
other => other,
});
i += 1;
}
result
}
fn try_extract_vec_topics(stmt: &Statement) -> Option<(String, Vec<String>)> {
if let Statement::Let { name, value: Expr::MacroCall { name: mac, args }, .. } = stmt {
if mac != "vec" { return None; }
let topics: Vec<String> = args.iter().skip(1) .filter_map(|a| match a {
Expr::MacroCall { name, args } if name == "symbol_short" => {
if let Some(Expr::Literal(Literal::Str(s))) = args.first() {
Some(s.clone())
} else { None }
}
_ => None,
})
.collect();
if !topics.is_empty() {
return Some((name.clone(), topics));
}
}
None
}
fn try_extract_event_publish(stmt: &Statement) -> Option<(String, Expr)> {
if let Statement::Expr(Expr::MethodChain { receiver, calls }) = stmt {
if !matches!(receiver.as_ref(), Expr::Var(n) if n == "env") { return None; }
if calls.len() != 2 { return None; }
if calls[0].name != "events" || calls[1].name != "publish" { return None; }
if calls[1].args.len() < 2 { return None; }
if let Expr::Var(var_name) = &calls[1].args[0] {
return Some((var_name.clone(), calls[1].args[1].clone()));
}
}
None
}
fn match_event_spec(
events: &[&stellar_xdr::curr::ScSpecEventV0],
topics: &[String],
data: &Expr,
) -> Option<Statement> {
for ev in events {
let ev_topics: Vec<String> = ev.params.iter()
.filter(|p| matches!(
p.location,
stellar_xdr::curr::ScSpecEventParamLocationV0::TopicList,
))
.map(|p| p.name.to_utf8_string_lossy())
.collect();
let data_fields: Vec<String> = ev.params.iter()
.filter(|p| matches!(
p.location,
stellar_xdr::curr::ScSpecEventParamLocationV0::Data,
))
.map(|p| p.name.to_utf8_string_lossy())
.collect();
let ev_name = ev.name.to_utf8_string_lossy();
let name_in_topics = topics.iter().any(|t| {
t.eq_ignore_ascii_case(&ev_name) || ev_name.to_lowercase().contains(&t.to_lowercase())
});
let topics_match = topics.len() == ev_topics.len() + 1 || topics.len() == ev_topics.len();
if (name_in_topics || topics_match) && !data_fields.is_empty() {
let fields: Vec<(String, Expr)> = data_fields.iter()
.map(|f| (f.clone(), data.clone()))
.collect();
let event_struct = Expr::StructLiteral {
name: ev_name,
fields,
};
return Some(Statement::Expr(Expr::MethodChain {
receiver: Box::new(event_struct),
calls: vec![MethodCall {
name: "publish".into(),
args: vec![Expr::Var("&env".into())],
}],
}));
}
}
None
}