use crate::core::ir::ApiSurface;
pub fn emit_service_app_wrappers(api: &ApiSurface, source_crate: &str) -> String {
let mut out = String::new();
if api.services.is_empty() {
return out;
}
for service in &api.services {
if service.registrations.is_empty() {
continue;
}
let service_name = &service.name;
let service_path = if service.rust_path.is_empty() {
format!("{source_crate}::{service_name}")
} else {
service.rust_path.clone()
};
let constructor = &service.constructor.name;
out.push_str(&format!(
"/// Wrapper for {service_name} service instance.\n\
/// Holds the inner service in a blocking mutex to allow \
mutable access\n\
/// across FFI boundaries.\n\
pub struct {service_name} {{\n\
\x20\x20\x20\x20pub inner: tokio::sync::Mutex<Option<{service_path}>>,\n\
}}\n\n"
));
out.push_str(&format!(
"impl {service_name} {{\n\
\x20\x20\x20\x20/// Create a new service instance.\n\
\x20\x20\x20\x20pub fn new() -> Self {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20Self {{\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20inner: tokio::sync::Mutex::new(Some({service_path}::{constructor}())),\n\
\x20\x20\x20\x20\x20\x20\x20\x20}}\n\
\x20\x20\x20\x20}}\n\n"
));
out.push_str(
" /// Configure the service.\n\
\x20\x20\x20\x20pub fn config(&mut self) {\n\
\x20\x20\x20\x20\x20\x20\x20\x20// Placeholder for future configuration.\n\
\x20\x20\x20\x20}\n\n",
);
out.push_str(
" /// Run the service (blocking, drives the Tokio runtime).\n\
\x20\x20\x20\x20///\n\
\x20\x20\x20\x20/// Returns an empty string on success or the error message.\n\
\x20\x20\x20\x20pub fn run(&mut self) -> String {\n\
\x20\x20\x20\x20\x20\x20\x20\x20let rt = match tokio::runtime::Runtime::new() {\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20Ok(rt) => rt,\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20Err(e) => return format!(\"runtime error: {:?}\", e),\n\
\x20\x20\x20\x20\x20\x20\x20\x20};\n\
\x20\x20\x20\x20\x20\x20\x20\x20rt.block_on(async {\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20let mut guard = self.inner.lock().await;\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20if let Some(app) = guard.take() {\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20match app.run().await {\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20Ok(()) => String::new(),\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20Err(e) => format!(\"{:?}\", e),\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20} else {\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\"service already consumed\".to_string()\n\
\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20\x20}\n\
\x20\x20\x20\x20\x20\x20\x20\x20})\n\
\x20\x20\x20\x20}\n\
}\n\n",
);
let service_snake_local = service_name.to_lowercase();
out.push_str(&format!(
"/// Free-function shim so the bridge declaration resolves.\n\
pub fn config(client: &mut {service_name}) {{ client.config() }}\n\n\
/// Free-function shim so the bridge declaration resolves.\n\
pub fn run(client: &mut {service_name}) -> String {{ client.run() }}\n\n\
/// Expose the wrapper's address as a usize for cross-bridge ptr handoff.\n\
pub fn {service_snake_local}_raw_ptr(client: &mut {service_name}) -> usize {{ client as *mut {service_name} as usize }}\n\n"
));
}
out
}