#[doc(hidden)]
pub use inventory;
pub use tyzen_macro::{Type, command};
pub mod utils;

use crate::utils::snake_to_camel;

pub mod ts_type;

pub struct CommandMeta {
    pub name: &'static str,
    pub params: &'static [(&'static str, fn() -> String)],
    pub return_type: fn() -> String,
}

pub struct TypeMeta {
    pub name: &'static str,
    pub ts_def: fn() -> String,
}

pub trait TsType {
    fn ts_name() -> String;
}

inventory::collect!(CommandMeta);
inventory::collect!(TypeMeta);

pub fn generate(output_path: &str) {
    generate_with_commands(output_path, write_command_declarations);
}

pub fn generate_with_commands(output_path: &str, write_commands: fn(&mut String)) {
    use std::fs;
    use std::path::PathBuf;

    let path = PathBuf::from(output_path);
    fs::create_dir_all(path.parent().unwrap()).unwrap();

    let mut ts = String::new();
    ts.push_str("// auto-generated by tyzen, do not edit\n");
    ts.push_str("/** autogen commands **/\n");
    write_commands(&mut ts);
    ts.push_str("\n/** autogen types **/\n");

    for t in inventory::iter::<TypeMeta> {
        ts.push_str(&(t.ts_def)());
        ts.push_str("\n");
    }
    ts.push_str("\n");
    ts.push_str("export type Result<T, E> = { status: \"ok\"; data: T } | { status: \"error\"; error: E }\n");

    fs::write(&path, ts).unwrap();
    println!("✅ tyzen generated: {}", output_path);
}

pub fn write_command_declarations(ts: &mut String) {
    ts.push_str("export type Commands = {\n");
    for cmd in inventory::iter::<CommandMeta> {
        let fn_name = snake_to_camel(cmd.name);
        let params_ts: Vec<String> = cmd
            .params
            .iter()
            .map(|(name, ty_fn)| format!("{}: {}", name, ty_fn()))
            .collect();

        ts.push_str(&format!(
            "  {}({}): Promise<{}>\n",
            fn_name,
            params_ts.join(", "),
            (cmd.return_type)(),
        ));
    }
    ts.push_str("}\n");
}
