use std::collections::HashMap;
use yog_data::Shape;
use yog_data::schema::{EnumShape, Field, StructShape};
use yog_sync::LazyLock;
#[derive(Clone)]
pub(crate) struct YogMethod {
pub params: Vec<Field>,
pub reply: Shape,
pub domain: &'static str,
pub address: &'static str,
}
#[derive(Default)]
pub(crate) struct Group {
pub methods: HashMap<String, Vec<YogMethod>>,
pub events: HashMap<String, String>,
pub planes: HashMap<String, &'static str>,
}
#[derive(Default)]
pub(crate) struct Surface {
pub groups: HashMap<String, Group>,
pub structs: HashMap<String, StructShape>,
pub enums: HashMap<String, EnumShape>,
pub args_planes: HashMap<String, (&'static str, &'static str)>,
}
const DOMAIN_SEP: char = '/';
const GROUP_SEP: &str = yog_script_ids::system::NAMESPACE_SEP;
const ARGS: &str = yog_script_ids::system::ARGS_SUFFIX;
pub(crate) static SURFACE: LazyLock<Surface> = LazyLock::new(Surface::make);
impl Surface {
fn make() -> Self {
let mut out = Surface::default();
for s in yog_event::services() {
let Shape::Struct(request) = &s.shape else {
continue;
};
let subject = request.fields.first().and_then(|f| {
yog_event::SUBJECTS
.iter()
.find(|(field, _)| *field == f.name)
});
let (group, params) = match subject {
Some((_, handle)) => (handle.to_string(), request.fields[1..].to_vec()),
None => (group_name(s.domain), request.fields.clone()),
};
for f in ¶ms {
out.note(&(f.shape)());
}
out.note(&s.reply);
let method = YogMethod {
params,
reply: s.reply.clone(),
domain: s.domain,
address: s.address,
};
out.groups
.entry(group)
.or_default()
.methods
.entry(s.name.to_string())
.or_default()
.push(method);
}
for e in yog_event::all() {
out.event(&e);
}
out
}
fn event(&mut self, e: &yog_event::EventInfo) {
let Shape::Struct(args) = &e.shape else {
return;
};
let plane = e.plane.split(DOMAIN_SEP).next().unwrap_or(e.plane);
let group = yog_codegen::pascal(plane);
let args_name = subgroup(&group, &format!("{}{ARGS}", e.name));
for f in &args.fields {
self.note(&(f.shape)());
}
self.structs.insert(args_name.clone(), args.clone());
self.args_planes
.insert(args_name.clone(), (e.plane, e.name));
let g = self.groups.entry(group).or_default();
g.events.insert(e.name.to_string(), args_name);
g.planes.insert(e.name.to_string(), e.plane);
}
fn note(&mut self, shape: &Shape) {
match shape {
Shape::Struct(s) if !self.structs.contains_key(s.name) => {
self.structs.insert(s.name.to_string(), s.clone());
for f in &s.fields {
self.note(&(f.shape)());
}
}
Shape::Enum(e) if !self.enums.contains_key(e.name) => {
self.enums.insert(e.name.to_string(), e.clone());
}
Shape::List(inner) | Shape::Map(inner) | Shape::Optional(inner) => self.note(&inner()),
_ => {}
}
}
pub fn knows(&self, name: &str) -> bool {
self.groups.contains_key(name)
|| self.structs.contains_key(name)
|| self.enums.contains_key(name)
}
}
fn group_name(domain: &str) -> String {
domain
.split(DOMAIN_SEP)
.map(yog_codegen::pascal)
.collect::<Vec<_>>()
.join(GROUP_SEP)
}
pub(crate) fn subgroup(group: &str, name: &str) -> String {
format!("{group}{GROUP_SEP}{name}")
}
#[cfg(test)]
mod tests;