use std::collections::{BTreeMap, BTreeSet};
use std::error::Error;

use configinator::{
    ActionAccessAuthOps, ActionAccessModelOps, ActionAccessPermission, ActionConfig, ActionLang,
    ActionTrigger, AuthConfig, ContentDefinition, IntegrationConfig, ModelConfig,
};

use heck::{ToLowerCamelCase, ToPascalCase};
use notatype::{Field, Kind};

const BASE_JAVASCRIPT_TOML: &str = include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/static/JavaScriptAction.toml"
));

const BINDINGS: &str = r#"
/// generated

use std::error::Error;
use serde::{Serialize, Deserialize};

#[unsafe(no_mangle)]
pub unsafe extern "C" fn alloc(len: usize) -> *mut u8 {
    let mut buf: Vec<u8> = Vec::with_capacity(len);

    let ptr = buf.as_mut_ptr();
    std::mem::forget(buf);

    ptr
}
"#;

fn rust_type_to_ts_type(rs_type: &str) -> String {
    if rs_type.contains("Vec<") {
        let mut rs_type = rs_type.replace("Vec<", "");
        rs_type = rs_type.replace(">", "");

        return format!("{}[]", rs_type);
    }

    match rs_type {
        "()" => "void".to_string(),
        "bool" => "boolean".to_string(),
        "u8" => "number".to_string(),
        "i8" => "number".to_string(),
        "u16" => "number".to_string(),
        "i16" => "number".to_string(),
        "u32" => "number".to_string(),
        "i32" => "number".to_string(),
        "u64" => "number".to_string(),
        "i64" => "number".to_string(),
        "f32" => "number".to_string(),
        "f64" => "number".to_string(),
        "[u8; 16]" => "number[]".to_string(),
        "Vec<u8>" => "number[]".to_string(),
        "String" | "&'a str" | "std::borrow::Cow<'a, str>" => "string".to_string(),
        _ => "void".to_string(),
    }
}

// todo: ref depth could be specified in config so it can be used to generate the structs.

fn struct_to_vec_push(
    parent_name: String,
    name: String,
    mut fields: Vec<Field>,
    model_map: &BTreeMap<String, ModelConfig>,
    include_refs: bool,
    cow_str: bool,
) -> String {
    let mut block = format!("let mut {}_vec = {}.start_vector();", name, parent_name);

    fields.sort_by(|a, b| a.idx.cmp(&b.idx));

    for field in fields {
        match field.kind {
            Kind::Ref {
                model: ref_model,
                field: _ref_field,
                many,
            } => {
                if include_refs
                    && let Some(_ref_model) = model_map.get(&ref_model) {
                    // will always be IDs
                    if many.is_some() {
                        block = format!(
                            "{}
                            let mut {}_vec = {}_vec.start_vector();
    
                            for uuid in {}.{} {{
                                {}_vec.push(&uuid[..]);
                            }}
                            
                            {}_vec.end_vector();
                            ",
                            block, field.name, name, name, field.name, field.name, field.name
                        );
                    } else {
                        block = format!(
                            "{}\n   {}_vec.push(&{}.{}[..]);",
                            block, name, name, field.name
                        );
                    }
                }
            }
            Kind::Object { name: _, fields: _ } => {}
            Kind::List { kind: _ } => {}
            Kind::Enum { name: _, opts: _ } => {
                block = format!("{}\n   {}_vec.push({}.{});", block, name, name, field.name);
            }
            Kind::Uuid => {
                block = format!(
                    "{}\n   {}_vec.push(&{}.{}[..]);",
                    block, name, name, field.name
                );
            }
            Kind::String => {
                if cow_str {
                    block = format!(
                        "{}\n   {}_vec.push({}.{}.as_ref());",
                        block, name, name, field.name
                    );
                } else {
                    block = format!("{}\n   {}_vec.push({}.{});", block, name, name, field.name);
                }
            }
            _ => {
                block = format!("{}\n   {}_vec.push({}.{});", block, name, name, field.name);
            }
        }
    }

    block = format!("{}\n   {}_vec.end_vector();", block, name);

    block
}

fn flex_to_struct_fill(
    arg: bool,
    name: String,
    mut fields: Vec<Field>,
    model_map: &BTreeMap<String, ModelConfig>,
    include_refs: bool,
    is_read: bool,
) -> Result<String, Box<dyn Error>> {
    fields.sort_by(|a, b| a.idx.cmp(&b.idx));

    let mut struct_fill = String::new();

    for field in fields {
        match field.kind {
            Kind::Ref {
                model,
                field: _ref_field,
                many: _many,
            } => {
                if include_refs
                    && let Some(ref_model) = model_map.get(&model) {
                    let filled_struct = flex_to_struct_fill(
                        true,
                        format!(
                            "{}{}",
                            if is_read { "Read" } else { "" },
                            ref_model.name.to_pascal_case(),
                        ),
                        ref_model.fields.clone(),
                        model_map,
                        false,
                        is_read,
                    )?;

                    // todo: handle lists with many
                    struct_fill =
                        format!("{}  {}: {},\n", struct_fill, field.name, filled_struct);
                }
            }
            _ => {
                let (_, nested_struct) = field.kind.to_stringified_type(&name.to_pascal_case());

                if let Some((nested_name, nested_fields)) = nested_struct {
                    let filled_struct = flex_to_struct_fill(
                        arg,
                        format!(
                            "{}{}",
                            if is_read { "Read" } else { "" },
                            nested_name.to_pascal_case(),
                        ),
                        nested_fields,
                        model_map,
                        false,
                        is_read,
                    )?;

                    // todo: handle lists
                    struct_fill = format!("{}  {}: {},\n", struct_fill, field.name, filled_struct)
                } else {
                    // todo: handle lists
                    if let Some(accessor) = field.kind.to_flexbuffer_accessor() {
                        struct_fill = format!(
                            "{}  {}: {}.as_vector().idx({}){}.into(),\n",
                            struct_fill,
                            field.name,
                            if arg { "args" } else { "claims" },
                            field.idx,
                            accessor
                        )
                    } else {
                        match field.kind {
                            Kind::List { kind } => {
                                if let Some(accessor) = kind.to_flexbuffer_accessor() {
                                    struct_fill = format!(
                                        "{}  {}: {}.as_vector().idx({}).as_vector().iter().map(|v| v{}.into()).collect::<Vec<{}>>(),\n",
                                        struct_fill,
                                        field.name,
                                        if arg { "args" } else { "claims" },
                                        field.idx,
                                        accessor,
                                        kind.to_stringified_type("").0,
                                    )
                                } else {
                                    // todo: handle lists of lists
                                }
                            }
                            _ => {
                                return Err("all other kinds should've been handled".into());
                            }
                        }
                    }
                }
            }
        }
    }

    struct_fill = format!("{} {{\n{}}}\n", name.to_pascal_case(), struct_fill);

    Ok(struct_fill)
}

fn fields_to_struct(
    config: &ActionConfig,
    name: String,
    mut fields: Vec<Field>,
    model_map: &BTreeMap<String, ModelConfig>,
    include_refs: bool,
    is_read: bool,
) -> (String, Vec<String>, String) {
    fields.sort_by(|a, b| a.idx.cmp(&b.idx));

    let mut struct_def = String::new();
    let mut nested_structs = vec![];

    let mut type_def = String::new();
    let mut type_defs = String::new();

    for field in fields {
        match field.kind {
            Kind::Ref {
                model,
                field: _ref_field,
                many: _many,
            } => {
                if include_refs
                    && let Some(ref_model) = model_map.get(&model) {
                    let ref_struct_name = format!(
                        "{}{}",
                        if is_read { "Read" } else { "" },
                        ref_model.name.to_pascal_case(),
                    );

                    let (nested_struct_def, double_nested_defs, external_type_defs) =
                        fields_to_struct(
                            config,
                            ref_struct_name.clone(),
                            ref_model.fields.clone(),
                            model_map,
                            false,
                            is_read,
                        );

                    nested_structs.push(nested_struct_def);

                    type_defs = format!("{}\n{}", type_defs, external_type_defs);

                    for double_nest in double_nested_defs {
                        nested_structs.push(double_nest);
                    }

                    // todo: handle lists (many)
                    struct_def = format!(
                        "{}  pub {}: {},\n",
                        struct_def,
                        field.name,
                        ref_struct_name.clone()
                    );

                    match config.lang {
                        ActionLang::Rust => {}
                        ActionLang::JavaScript => {
                            type_def = format!(
                                "{}  {}: {},\n",
                                type_def,
                                field.name,
                                ref_struct_name.clone()
                            );
                        }
                    }
                }
            }
            _ => {
                let (type_name, nested_struct) =
                    field.kind.to_stringified_type(&name.to_pascal_case());

                if let Some((nested_name, nested_fields)) = nested_struct {
                    let (nested_struct_def, double_nested_defs, external_type_defs) =
                        fields_to_struct(
                            config,
                            format!("{}{}", if is_read { "Read" } else { "" }, nested_name),
                            nested_fields,
                            model_map,
                            false,
                            is_read,
                        );

                    nested_structs.push(nested_struct_def);

                    type_defs = format!("{}\n{}", type_defs, external_type_defs);

                    for double_nest in double_nested_defs {
                        nested_structs.push(double_nest);
                    }

                    // todo: handle lists
                    struct_def = format!("{}  pub {}: {},\n", struct_def, field.name, nested_name);

                    match config.lang {
                        ActionLang::Rust => {}
                        ActionLang::JavaScript => {
                            type_def = format!("{} {}: {},\n", type_def, field.name, nested_name)
                        }
                    }
                } else {
                    struct_def = format!("{}  pub {}: {},\n", struct_def, field.name, type_name);

                    match config.lang {
                        ActionLang::Rust => {}
                        ActionLang::JavaScript => {
                            type_def = format!(
                                "{} {}: {},\n",
                                type_def,
                                field.name,
                                rust_type_to_ts_type(&type_name)
                            )
                        }
                    }
                }
            }
        }
    }

    struct_def = format!(
        "#[derive(Debug, Serialize, Deserialize)]
pub struct {} {{\n{}}}\n",
        name.to_pascal_case(),
        struct_def
    );

    match config.lang {
        ActionLang::Rust => {}
        ActionLang::JavaScript => {
            type_def = format!("interface {} {{\n{}}}\n", name.to_pascal_case(), type_def);

            type_defs = format!("{}\n{}", type_defs, type_def);
        }
    }

    (struct_def, nested_structs, type_defs)
}

fn fields_to_struct_lifetime(
    config: &ActionConfig,
    name: String,
    mut fields: Vec<Field>,
    model_map: &BTreeMap<String, ModelConfig>,
    include_refs: bool,
    is_write: bool,
    cow_str: bool,
) -> (String, Vec<String>, String) {
    fields.sort_by(|a, b| a.idx.cmp(&b.idx));

    let mut struct_def = String::new();
    let mut nested_structs = vec![];

    let mut type_def = String::new();
    let mut type_defs = String::new();

    let mut needs_lifetime = false;

    for field in fields {
        match field.kind {
            Kind::Ref {
                model,
                field: _ref_field,
                many,
            } => {
                if is_write {
                    if many.is_some() {
                        struct_def =
                            format!("{}  pub {}: Vec<[u8; 16]>,\n", struct_def, field.name);

                        match config.lang {
                            ActionLang::Rust => {}
                            ActionLang::JavaScript => {
                                type_def = format!("{}  {}: number[][],\n", type_def, field.name)
                            }
                        }
                    } else {
                        struct_def = format!("{}  pub {}: [u8; 16],\n", struct_def, field.name);

                        match config.lang {
                            ActionLang::Rust => {}
                            ActionLang::JavaScript => {
                                type_def = format!("{}  {}: number[],\n", type_def, field.name)
                            }
                        }
                    }
                } else if include_refs
                    && let Some(ref_model) = model_map.get(&model) {
                    let (nested_struct_def, double_nested_defs, external_type_defs) =
                        fields_to_struct_lifetime(
                            config,
                            ref_model.name.clone(),
                            ref_model.fields.clone(),
                            model_map,
                            false,
                            is_write,
                            cow_str,
                        );

                    nested_structs.push(nested_struct_def);

                    type_defs = format!("{}\n{}", type_defs, external_type_defs);

                    for double_nest in double_nested_defs {
                        nested_structs.push(double_nest);
                    }

                    if many.is_some() {
                        struct_def = format!(
                            "{}  pub {}: Vec<{}>,\n",
                            struct_def, field.name, ref_model.name
                        );

                        match config.lang {
                            ActionLang::Rust => {}
                            ActionLang::JavaScript => {
                                type_def = format!(
                                    "{}  {}: {}[],\n",
                                    type_def, field.name, ref_model.name
                                )
                            }
                        }
                    } else {
                        struct_def = format!(
                            "{}  pub {}: {},\n",
                            struct_def, field.name, ref_model.name
                        );

                        match config.lang {
                            ActionLang::Rust => {}
                            ActionLang::JavaScript => {
                                type_def = format!(
                                    "{}  {}: {},\n",
                                    type_def, field.name, ref_model.name
                                )
                            }
                        }
                    }
                }
            }
            _ => {
                let (type_name, nested_struct) =
                    field
                        .kind
                        .to_stringified_type_w_lifetime(&name.to_pascal_case(), "a", cow_str);

                if let Some((nested_name, nested_fields)) = nested_struct {
                    let (nested_struct_def, double_nested_defs, external_type_defs) =
                        fields_to_struct_lifetime(
                            config,
                            nested_name.clone(),
                            nested_fields,
                            model_map,
                            false,
                            is_write,
                            cow_str,
                        );

                    nested_structs.push(nested_struct_def);

                    type_defs = format!("{}\n{}", type_defs, external_type_defs);

                    for double_nest in double_nested_defs {
                        nested_structs.push(double_nest);
                    }

                    struct_def = format!(
                        "{}  #[serde(borrow)]\n    pub {}: {}<'a>,\n",
                        struct_def, field.name, nested_name
                    );

                    match config.lang {
                        ActionLang::Rust => {}
                        ActionLang::JavaScript => {
                            type_def = format!("{}  {}: {},\n", type_def, field.name, nested_name)
                        }
                    }
                } else if type_name.contains("'a") {
                    struct_def = format!(
                        "{}  #[serde(borrow)]\n  pub {}: {},\n",
                        struct_def, field.name, type_name
                    );

                    match config.lang {
                        ActionLang::Rust => {}
                        ActionLang::JavaScript => {
                            type_def = format!(
                                "{}  {}: {},\n",
                                type_def,
                                field.name,
                                rust_type_to_ts_type(&type_name)
                            )
                        }
                    }
                } else {
                    struct_def =
                        format!("{}  pub {}: {},\n", struct_def, field.name, type_name);

                    match config.lang {
                        ActionLang::Rust => {}
                        ActionLang::JavaScript => {
                            type_def = format!(
                                "{}  {}: {},\n",
                                type_def,
                                field.name,
                                rust_type_to_ts_type(&type_name)
                            )
                        }
                    }
                }
            }
        }
    }

    if struct_def.contains("'a") {
        needs_lifetime = true;
    }

    struct_def = format!(
        "#[derive(Debug, Serialize, Deserialize)]
pub struct {}{} {{\n{}}}\n",
        name.to_pascal_case(),
        if needs_lifetime { "<'a>" } else { "" },
        struct_def
    );

    match config.lang {
        ActionLang::Rust => {}
        ActionLang::JavaScript => {
            type_def = format!("interface {} {{\n{}}}\n", name.to_pascal_case(), type_def);

            type_defs = format!("{}\n{}", type_defs, type_def);
        }
    }

    (struct_def, nested_structs, type_defs)
}

#[allow(clippy::type_complexity)]
pub fn generate_action_bindings(
    action_config: ActionConfig,
    model_map: &BTreeMap<String, ModelConfig>,
    content_map: &BTreeMap<String, ContentDefinition>,
    integrations_map: &BTreeMap<String, IntegrationConfig>,
    auth_config: &Option<AuthConfig>,
    domain: &String,
) -> Result<(String, Option<(String, String, String)>), Box<dyn Error>> {
    let use_cow_str = match action_config.lang {
        ActionLang::JavaScript => true,
        ActionLang::Rust => false,
    };

    let mut js_base = String::new();
    let mut js_body = String::new();
    let mut js_types = String::new();

    match action_config.lang {
        ActionLang::Rust => {}
        ActionLang::JavaScript => {
            js_base = r#"/// generated
const JS: &'static str = concat!(
    r#"globalThis.console = {
  debug(...v) { globalThis.__print(1, `${v.join(" ")}`); },
  log(...v) { globalThis.__print(2, `${v.join(" ")}`); },
  info(...v) { globalThis.__print(2, `${v.join(" ")}`); },
  warn(...v) { globalThis.__print(3, `${v.join(" ")}`); },
  error(...v) { globalThis.__print(4, `${v.join(" ")}`); },
};
globalThis.Ordinary = {
    Get: {
        UUID: 0,
        STRING: 1,
        URL: 2,
    },
    Expression: {
        GTE: 0,
        LTE: 1,
        EQ: 2,
        GT: 3,
        LT: 4,
        BEGINS_WITH: 5,
    },
    Query: {
        UUID: 0,
        STRING: 1,
        URL: 2,
        BOOL: 3,
        F32: 4,
        F64: 5,
        U8: 6,
        U16: 7,
        U32: 8,
        U64: 9,
        I8: 10,
        I16: 11,
        I32: 12,
        I64: 13,
        TIMESTAMP_S: 14,
    },
    Search: {
        UUID: 0,
        STRING: 1,
        URL: 2,
        BOOL: 3,
        F32: 4,
        F64: 5,
        U8: 6,
        U16: 7,
        U32: 8,
        U64: 9,
        I8: 10,
        I16: 11,
        I32: 12,
        I64: 13,
        TIMESTAMP_S: 14,
        MARKDOWN: 15,
        JSON: 16,
    },"#
                .to_string();

            js_body = r#"use rquickjs::{Context, Ctx, Function, Result, Runtime, Value};

fn print(lvl: u8, s: String) {
    ordinary::trace(lvl, &s).unwrap();
}

fn main() -> Result<()> {
    std::panic::set_hook(Box::new(|info| print(4, info.to_string())));

    let rt = Runtime::new()?;
    let ctx = Context::full(&rt)?;

    ctx.with(|ctx| -> Result<()> {
        let global = ctx.globals();

        global.set(
            "__print",
            Function::new(ctx.clone(), print)?.with_name("__print")?,
        )?;"#
                .to_string();

            js_types = r#"// generated
declare namespace Ordinary {
    const Domain: string

    const Get: {
        UUID: 0,
        STRING: 1,
        URL: 2,
    }
    const Expression: {
        GTE: 0,
        LTE: 1,
        EQ: 2,
        GT: 3,
        LT: 4,
        BEGINS_WITH: 5,
    }
    const Query: {
        UUID: 0,
        STRING: 1,
        URL: 2,
        BOOL: 3,
        F32: 4,
        F64: 5,
        U8: 6,
        U16: 7,
        U32: 8,
        U64: 9,
        I8: 10,
        I16: 11,
        I32: 12,
        I64: 13,
        TIMESTAMP_S: 14,
    }
    const Search: {
        UUID: 0,
        STRING: 1,
        URL: 2,
        BOOL: 3,
        F32: 4,
        F64: 5,
        U8: 6,
        U16: 7,
        U32: 8,
        U64: 9,
        I8: 10,
        I16: 11,
        I32: 12,
        I64: 13,
        TIMESTAMP_S: 14,
        MARKDOWN: 15,
        JSON: 16,
    }
"#
                .to_string();
        }
    };

    let mut defined_models_lifetime = BTreeSet::new();
    let mut defined_models = BTreeSet::new();
    let mut defined_content = BTreeSet::new();

    let mut block_defs = vec![];

    let mut ffi_block = r#"unsafe extern "C" {
    fn host_get_input() -> i64;
    fn host_set_output(ptr: i32, len: i32) -> i32;

    fn host_trace(ptr: i32, len: i32) -> i32;
"#.to_string();

    let mut claims_struct_fill = String::new();

    if let Some(auth_config) = auth_config {
        let (top, nested, external_type_defs) = fields_to_struct_lifetime(
            &action_config,
            "claims".to_string(),
            auth_config.access_token.claims.clone(),
            model_map,
            false,
            false,
            use_cow_str,
        );

        match action_config.lang {
            ActionLang::Rust => {}
            ActionLang::JavaScript => js_types = format!("{}\n{}", js_types, external_type_defs),
        }

        let mut block = top;

        for nest in nested {
            block = format!("{}\n{}", block, nest);
        }

        block_defs.push(block);

        claims_struct_fill = flex_to_struct_fill(
            false,
            "claims".to_string(),
            auth_config.access_token.claims.clone(),
            model_map,
            false,
            false,
        )?
    }

    if action_config.protected.is_some() {
        let (accepts_type_name, accepts_struct_def) = action_config.accepts.to_stringified_type("");

        let (accepts_input_name, accepts_accessor) =
            if let Some((struct_name, struct_fields)) = accepts_struct_def {
                let (top, nested, external_type_defs) = fields_to_struct(
                    &action_config,
                    struct_name.clone(),
                    struct_fields.clone(),
                    model_map,
                    false,
                    false,
                );

                match action_config.lang {
                    ActionLang::Rust => {}
                    ActionLang::JavaScript => {
                        js_types = format!("{}\n{}", js_types, external_type_defs)
                    }
                }

                block_defs.push(top);

                for nest in nested {
                    block_defs.push(nest);
                }

                let fill = flex_to_struct_fill(
                    true,
                    struct_name.clone(),
                    struct_fields,
                    model_map,
                    false,
                    false,
                )?;

                (struct_name, fill)
            } else {
                (
                    accepts_type_name,
                    format!(
                        "args{}.into()",
                        action_config.accepts.to_flexbuffer_accessor().unwrap()
                    ),
                )
            };

        let block = format!(
            r#"pub fn recv_in() -> Result<({}, Claims), Box<dyn Error>> {{
    let input = unsafe {{ host_get_input() }};

    let input_ptr = (input >> 32) as i32;
    let input_len = input as i32;

    let input = unsafe {{
        Vec::from_raw_parts(input_ptr as *mut u8, input_len as usize, input_len as usize)
    }};

    let root = flexbuffers::Reader::get_root(&input[..])?;

    let args = root.as_vector().idx(0);
    let claims = root.as_vector().idx(1);

    Ok(({}, {}))
}}"#,
            accepts_input_name, accepts_accessor, claims_struct_fill
        );

        block_defs.push(block);

        match action_config.lang {
            ActionLang::Rust => {}
            ActionLang::JavaScript => {
                js_body = format!(
                    r#"{}
                if let Ok((params, claims)) = ordinary::recv_in() {{
                    if let Ok(params) = rquickjs_serde::to_value(ctx.clone(), params) {{
                        global.set("__params", params)?;
                    }}
                    if let Ok(claims) = rquickjs_serde::to_value(ctx.clone(), claims) {{
                        global.set("__claims", claims)?;
                    }}
                }}
                "#,
                    js_body
                );
            }
        };
    } else {
        let (accepts_type_name, accepts_struct_def) = action_config.accepts.to_stringified_type("");

        let (accepts_input_name, accepts_accessor) =
            if let Some((struct_name, struct_fields)) = accepts_struct_def {
                let (top, nested, external_type_defs) = fields_to_struct(
                    &action_config,
                    struct_name.clone(),
                    struct_fields.clone(),
                    model_map,
                    false,
                    false,
                );

                match action_config.lang {
                    ActionLang::Rust => {}
                    ActionLang::JavaScript => {
                        js_types = format!("{}\n{}", js_types, external_type_defs)
                    }
                }

                block_defs.push(top);

                for nest in nested {
                    block_defs.push(nest);
                }

                let fill = flex_to_struct_fill(
                    true,
                    struct_name.clone(),
                    struct_fields,
                    model_map,
                    false,
                    false,
                )?;

                (struct_name, fill)
            } else if let Some(accessor) = action_config.accepts.to_flexbuffer_accessor() {
                (accepts_type_name, format!("args{}.into()", accessor))
            } else {
                (accepts_type_name, "()".to_string())
            };

        let block = format!(
            r#"pub fn recv_in() -> Result<{}, Box<dyn Error>> {{
        let input = unsafe {{ host_get_input() }};
    
        let input_ptr = (input >> 32) as i32;
        let input_len = input as i32;
    
        let input = unsafe {{
            Vec::from_raw_parts(input_ptr as *mut u8, input_len as usize, input_len as usize)
        }};
    
        let root = flexbuffers::Reader::get_root(&input[..])?;
    
        let args = root.as_vector().idx(0);
    
        Ok({})
    }}"#,
            accepts_input_name, accepts_accessor
        );

        block_defs.push(block);

        match action_config.lang {
            ActionLang::Rust => {}
            ActionLang::JavaScript => {
                js_body = format!(
                    r#"{}
        if let Ok(params) = ordinary::recv_in() {{
            if let Ok(params) = rquickjs_serde::to_value(ctx.clone(), params) {{
                global.set("__params", params)?;
            }}
        }}
        "#,
                    js_body
                );
            }
        };
    }

    let (returns_type_name, returns_struct_def) = action_config
        .returns
        .to_stringified_type_w_lifetime("", "a", use_cow_str);

    let (returns_name, returns_builder) = if let Some((struct_name, struct_fields)) =
        returns_struct_def
    {
        let (top, nested, external_type_defs) = fields_to_struct_lifetime(
            &action_config,
            struct_name.clone(),
            struct_fields.clone(),
            model_map,
            false,
            false,
            use_cow_str,
        );

        match action_config.lang {
            ActionLang::Rust => {}
            ActionLang::JavaScript => js_types = format!("{}\n{}", js_types, external_type_defs),
        }

        block_defs.push(top);

        for nest in nested {
            block_defs.push(nest);
        }

        let fill = struct_to_vec_push(
            "builder".into(),
            "output".to_string(),
            struct_fields,
            model_map,
            true,
            use_cow_str,
        );

        (struct_name, fill)
    } else if let Kind::Uuid = action_config.returns {
        (
            returns_type_name,
            "builder.build_singleton(&output[..]);".to_string(),
        )
    } else {
        (
            returns_type_name,
            "builder.build_singleton(output);".to_string(),
        )
    };

    let block = format!(
        r#"pub fn send_out<'a>(output: {}) -> Result<(), Box<dyn Error>> {{
    let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);

    {}

    let mut output = builder.view().to_vec();

    let output_len = output.len();
    let output_ptr = output.as_mut_ptr();

    std::mem::forget(output);
    let res = unsafe {{ host_set_output(output_ptr as i32, output_len as i32) }};

    if res == 1 {{
        Ok(())
    }} else {{
        Err("failed to write to output".into())
    }}
}}"#,
        returns_name, returns_builder
    );

    block_defs.push(block);

    let block = r#"pub fn trace(level: u8, val: &str) -> Result<(), Box<dyn Error>> {
    let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);
    let mut log = builder.start_vector();

    log.push(level);
    log.push(val);

    log.end_vector();

    let mut output = builder.view().to_vec();

    let output_len = output.len();
    let output_ptr = output.as_mut_ptr();

    std::mem::forget(output);
    let res = unsafe { host_trace(output_ptr as i32, output_len as i32) };

    if res == 1 {
        Ok(())
    } else {
        Err("failed to write trace".into())
    }
}"#.to_string();

    block_defs.push(block);

    let block = r#"pub enum Get {
    Uuid([u8; 16]),
    String(String),
    Url(String),
}

impl Get {
    pub fn as_bytes(&self) -> Vec<u8> {
        match self {
            Self::Uuid(v) => v.to_vec(),
            Self::String(v) | Self::Url(v) => v.as_bytes().to_vec(),
        }
    }
}

pub enum TimeUnit {
    Seconds(i64),
}

pub enum Query {
    Bool(bool),
    Uuid([u8; 16]),
    String(String),
    Url(String),
    Timestamp(TimeUnit),
    U8(u8),
    U16(u16),
    U32(u32),
    U64(u64),
    I8(i8),
    I16(i16),
    I32(i32),
    I64(i64),
    F32(f32),
    F64(f64),
}

impl Query {
    pub fn as_bytes(&self) -> Vec<u8> {
        match self {
            Self::Bool(v) => {
                if *v {
                    vec![1]
                } else {
                    vec![0]
                }
            }
            Self::Uuid(v) => v.to_vec(),
            Self::F32(v) => v.to_be_bytes().to_vec(),
            Self::F64(v) => v.to_be_bytes().to_vec(),
            Self::U8(v) => v.to_be_bytes().to_vec(),
            Self::U16(v) => v.to_be_bytes().to_vec(),
            Self::U32(v) => v.to_be_bytes().to_vec(),
            Self::U64(v) => v.to_be_bytes().to_vec(),
            Self::I8(v) => v.to_be_bytes().to_vec(),
            Self::I16(v) => v.to_be_bytes().to_vec(),
            Self::I32(v) => v.to_be_bytes().to_vec(),
            Self::I64(v) => v.to_be_bytes().to_vec(),
            Self::String(v) | Self::Url(v) => v.as_bytes().to_vec(),
            Self::Timestamp(v) => match v {
                TimeUnit::Seconds(v) => v.to_be_bytes().to_vec(),
            },
        }
    }
}

pub enum Expression {
    Gte,
    Gt,
    Lte,
    Lt,
    Eq,
    BeginsWith,
}

impl Expression {
    pub fn as_byte(&self) -> u8 {
        match self {
            Self::Gte => 0,
            Self::Lte => 1,
            Self::Eq => 2,
            Self::Gt => 3,
            Self::Lt => 4,
            Self::BeginsWith => 5,
        }
    }
}

pub enum Search {
    Bool(bool),
    Uuid([u8; 16]),
    String(String),
    Url(String),
    Json(String),
    Markdown(String),
    Timestamp(TimeUnit),
    U8(u8),
    U16(u16),
    U32(u32),
    U64(u64),
    I8(i8),
    I16(i16),
    I32(i32),
    I64(i64),
    F32(f32),
    F64(f64),
}

impl Search {
    pub fn as_bytes(&self) -> Vec<u8> {
        match self {
            Self::Bool(v) => {
                if *v {
                    vec![1]
                } else {
                    vec![0]
                }
            }
            Self::Uuid(v) => v.to_vec(),
            Self::F32(v) => v.to_be_bytes().to_vec(),
            Self::F64(v) => v.to_be_bytes().to_vec(),
            Self::U8(v) => v.to_be_bytes().to_vec(),
            Self::U16(v) => v.to_be_bytes().to_vec(),
            Self::U32(v) => v.to_be_bytes().to_vec(),
            Self::U64(v) => v.to_be_bytes().to_vec(),
            Self::I8(v) => v.to_be_bytes().to_vec(),
            Self::I16(v) => v.to_be_bytes().to_vec(),
            Self::I32(v) => v.to_be_bytes().to_vec(),
            Self::I64(v) => v.to_be_bytes().to_vec(),
            Self::String(v) | Self::Markdown(v) | Self::Json(v) | Self::Url(v) => {
                v.as_bytes().to_vec()
            }
            Self::Timestamp(v) => match v {
                TimeUnit::Seconds(v) => v.to_be_bytes().to_vec(),
            },
        }
    }
}"#
        .to_string();

    block_defs.push(block);

    for trigger in &action_config.triggered_by {
        match trigger {
            ActionTrigger::Registration => match &action_config.accepts {
                Kind::String => (),
                _ => {
                    return Err("triggered by 'Registration' must accept kind String".into());
                }
            },
            ActionTrigger::Login => {}
            // todo
            // ActionTrigger::Job { expression: _ } => match &action_config.accepts {
            //     Kind::Void => (),
            //     _ => {
            //         return Err("triggered by 'Job' must accept kind Void".into());
            //     }
            // },
            ActionTrigger::Ordinary => {
                //
            }
            ActionTrigger::Form {
                route: _,
                method: _,
                redirect: _,
            } => match &action_config.accepts {
                Kind::Json | Kind::List { kind: _ } | Kind::Object { name: _, fields: _ } => (),
                _ => {
                    return Err(
                        "triggered by 'Rest' must accept kinds 'Json' | 'List' | 'Object'".into(),
                    );
                }
            },
            ActionTrigger::Rest {
                route: _,
                method: _,
            } => match &action_config.accepts {
                Kind::Void
                | Kind::Json
                | Kind::List { kind: _ }
                | Kind::Object { name: _, fields: _ } => (),
                _ => {
                    return Err(
                        "triggered by 'Rest' must accept kinds 'Void' | 'Json' | 'List' | 'Object'"
                            .into(),
                    );
                }
            },
            // todo
            // ActionTrigger::Grpc => {}
            ActionTrigger::Content { name } => {
                if let Some(content_def) = content_map.get(name) {
                    let mut content_fields_map = BTreeMap::new();

                    for field in &content_def.fields {
                        content_fields_map.insert(field.name.clone(), field.kind.clone());
                    }

                    match &action_config.accepts {
                        Kind::Object { name: _, fields } => {
                            if fields.len() != 2 {
                                return Err("triggered by 'Content' must accept kind Object with exactly two fields".into());
                            }

                            for field in fields {
                                match &field.kind {
                                    Kind::List { kind } => match &**kind {
                                        Kind::Object { name: _, fields } => {
                                            for field in fields {
                                                if let Some(kind) =
                                                    content_fields_map.get(&field.name)
                                                    && kind != &field.kind {
                                                    return Err(format!(
                                                        "field '{}' is not of kind '{}'",
                                                        field.name,
                                                        kind.to_stringified_type("").0
                                                    )
                                                        .into());
                                                }
                                            }
                                        }
                                        _ => {
                                            return Err(format!(
                                                "List '{}' is not of kind Object.",
                                                field.name
                                            )
                                                .into());
                                        }
                                    },
                                    _ => {
                                        return Err(format!(
                                            "field '{}' is not of kind List.",
                                            field.name
                                        )
                                            .into());
                                    }
                                }
                            }
                        }
                        _ => return Err("triggered by 'Content' must accept kind Object".into()),
                    }
                }
            } // todo
            // ActionTrigger::Model { name: _, op: _ } => {
            //     // verify that the accepts is consistent with the model definition's fields
            //     // and whether a "before/after" is necessary based on op.
            // }
        }
    }

    let mut auth_has_been_def = false;
    let mut integration_has_been_def = false;
    let mut model_insert_has_been_def = false;
    let mut model_update_has_been_def = false;
    let mut model_delete_has_been_def = false;
    let mut model_get_has_been_def = false;
    let mut model_query_has_been_def = false;
    let mut model_search_has_been_def = false;

    for access in &action_config.access {
        match access {
            ActionAccessPermission::Auth { ops } => {
                for op in ops {
                    match op {
                        ActionAccessAuthOps::SetTokenFields => {
                            ffi_block = format!(
                                "{}\nfn host_auth_set_claims(ptr: i32, len: i32) -> i32;",
                                ffi_block
                            );

                            if let Some(auth_config) = auth_config
                                && !auth_has_been_def {
                                auth_has_been_def = true;

                                let block = format!(
                                    r#"
    pub fn set_claims(account: &str, claims: Claims) -> Result<(), Box<dyn Error>> {{
        let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);
        let mut req = builder.start_vector();
    
        req.push(account);
    
        {}
    
        req.end_vector();
    
        let mut req = builder.view().to_vec();
    
        let len = req.len();
        let ptr = req.as_mut_ptr();
    
        std::mem::forget(req);
    
        unsafe {{ host_auth_set_claims(ptr as i32, len as i32) }};
    
        Ok(())
    }}
    "#,
                                    struct_to_vec_push(
                                        "req".into(),
                                        "claims".into(),
                                        auth_config.access_token.claims.clone(),
                                        model_map,
                                        false,
                                        use_cow_str,
                                    )
                                );

                                block_defs.push(block);

                                match &action_config.lang {
                                    ActionLang::Rust => {}
                                    ActionLang::JavaScript => {
                                        js_base = format!(
                                            r#"{}
                  setTokenClaims(account, claims) {{
                    globalThis.__setTokenClaims(account, JSON.stringify(claims));
                  }},"#,
                                            js_base,
                                        );

                                        js_body = format!(
                                            r#"{}
                        global.set(
                            "__setTokenClaims",
                            Function::new(
                                ctx.clone(),
                                |_ctx: Ctx, account: String, claims: String| -> Result<()> {{
                                    let claims: ordinary::Claims =
                                        serde_json::from_str(&claims).expect("failed to deserialize claims");
                
                                    ordinary::set_claims(&account, claims).expect("claims not set");
                
                                    Ok(())
                                }},
                            )?
                            .with_name("__setTokenClaims"),
                        )?;"#,
                                            js_body,
                                        );

                                        js_types = format!(
                                            "{}
        function setTokenClaims(account: string, claims: Claims): void;",
                                            js_types,
                                        );
                                    }
                                };
                            }
                        }
                    }
                }
            }
            ActionAccessPermission::Model { name, ops } => {
                for op in ops {
                    match op {
                        ActionAccessModelOps::Insert => {
                            if !model_insert_has_been_def {
                                ffi_block = format!(
                                    "{}\nfn host_model_insert(ptr: i32, len: i32) -> i64;",
                                    ffi_block
                                );

                                model_insert_has_been_def = true;
                            }

                            if let Some(model_config) = model_map.get(name) {
                                let mut block = String::new();

                                let input_model_struct_name =
                                    format!("Write{}", model_config.name.clone().to_pascal_case());

                                if !defined_models_lifetime.contains(&input_model_struct_name) {
                                    let (top, nested, external_type_defs) =
                                        fields_to_struct_lifetime(
                                            &action_config,
                                            input_model_struct_name.clone(),
                                            model_config.fields.clone(),
                                            model_map,
                                            true,
                                            true,
                                            use_cow_str,
                                        );

                                    match action_config.lang {
                                        ActionLang::Rust => {}
                                        ActionLang::JavaScript => {
                                            js_types =
                                                format!("{}\n{}", js_types, external_type_defs)
                                        }
                                    }

                                    defined_models_lifetime.insert(input_model_struct_name.clone());

                                    block = top;

                                    for nest in nested {
                                        block = format!("{}\n{}", block, nest);
                                    }
                                }

                                block = format!(
                                    r#"{}
pub fn insert_{}_model({}: {}) -> Result<[u8; 16], Box<dyn Error>> {{
    let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);
    let mut req = builder.start_vector();

    req.push({});
    
    {}
    
    req.end_vector();

    let mut req = builder.view().to_vec();

    let len = req.len();
    let ptr = req.as_mut_ptr();

    std::mem::forget(req);

    let uuid = unsafe {{ host_model_insert(ptr as i32, len as i32) }};

    if uuid == 0 {{
        return Err("failed to insert".into());
    }}

    let uuid_ptr = (uuid >> 32) as i32;
    let uuid_len = uuid as i32;

    let uuid =
        unsafe {{ Vec::from_raw_parts(uuid_ptr as *mut u8, uuid_len as usize, uuid_len as usize) }};

    let mut out = [0u8; 16];
    out[0..16].copy_from_slice(&uuid[..]);

    Ok(out)
}}"#,
                                    block,
                                    model_config.name,
                                    model_config.name,
                                    input_model_struct_name,
                                    model_config.idx,
                                    struct_to_vec_push(
                                        "req".into(),
                                        model_config.name.clone(),
                                        model_config.fields.clone(),
                                        model_map,
                                        true,
                                        use_cow_str,
                                    )
                                );

                                block_defs.push(block);

                                match action_config.lang {
                                    ActionLang::Rust => {}
                                    ActionLang::JavaScript => {
                                        js_base = format!(
                                            r#"{}
  insert{}Model(obj) {{
    globalThis.__insert{}Model(JSON.stringify(obj));
    return globalThis.__insert{}ModelResult;
  }},"#,
                                            js_base,
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case()
                                        );

                                        js_body = format!(
                                            r#"{}
        global.set(
            "__insert{}Model",
            Function::new(ctx.clone(), |ctx: Ctx, val: String| -> Result<()> {{
                let object: ordinary::{} =
                    serde_json::from_str(val.as_str()).expect("failed to deserialize object");

                if let Ok(uuid) = ordinary::insert_{}_model(object) {{
                    let global = ctx.globals();
                    global.set("__insert{}ModelResult", uuid.to_vec())?;
                }}

                Ok(())
            }})?
            .with_name("__insert{}Model"),
        )?;"#,
                                            js_body,
                                            model_config.name.to_pascal_case(),
                                            input_model_struct_name,
                                            model_config.name,
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case(),
                                        );

                                        js_types = format!(
                                            "{}
    function insert{}Model({}: {}): number[];",
                                            js_types,
                                            model_config.name.to_pascal_case(),
                                            model_config.name,
                                            model_config.name.to_pascal_case()
                                        );
                                    }
                                };
                            }
                        }
                        ActionAccessModelOps::Update => {
                            if !model_update_has_been_def {
                                ffi_block = format!(
                                    "{}\nfn host_model_update(ptr: i32, len: i32) -> i32;",
                                    ffi_block
                                );

                                model_update_has_been_def = true;
                            }

                            if let Some(model_config) = model_map.get(name) {
                                let mut block = String::new();

                                let input_model_struct_name =
                                    format!("Write{}", model_config.name.clone().to_pascal_case());

                                if !defined_models_lifetime.contains(&input_model_struct_name) {
                                    let (top, nested, external_type_defs) =
                                        fields_to_struct_lifetime(
                                            &action_config,
                                            input_model_struct_name.clone(),
                                            model_config.fields.clone(),
                                            model_map,
                                            true,
                                            true,
                                            use_cow_str,
                                        );

                                    match action_config.lang {
                                        ActionLang::Rust => {}
                                        ActionLang::JavaScript => {
                                            js_types =
                                                format!("{}\n{}", js_types, external_type_defs)
                                        }
                                    }

                                    defined_models_lifetime.insert(input_model_struct_name.clone());

                                    block = top;

                                    for nest in nested {
                                        block = format!("{}\n{}", block, nest);
                                    }
                                }

                                block = format!(
                                    r#"{}
pub fn update_{}_model(uuid: [u8; 16], {}: {}) -> Result<(), Box<dyn Error>> {{
    let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);
    let mut req = builder.start_vector();

    req.push({});
    req.push(&uuid[..]);
    
    {}
    
    req.end_vector();

    let mut req = builder.view().to_vec();

    let len = req.len();
    let ptr = req.as_mut_ptr();

    std::mem::forget(req);

    let val = unsafe {{ host_model_update(ptr as i32, len as i32) }};

    if val == 0 {{
        return Err("failed to update".into());
    }}

    Ok(())
}}"#,
                                    block,
                                    model_config.name,
                                    model_config.name,
                                    input_model_struct_name,
                                    model_config.idx,
                                    struct_to_vec_push(
                                        "req".into(),
                                        model_config.name.clone(),
                                        model_config.fields.clone(),
                                        model_map,
                                        true,
                                        use_cow_str,
                                    )
                                );

                                block_defs.push(block);

                                match action_config.lang {
                                    ActionLang::Rust => {}
                                    ActionLang::JavaScript => {
                                        js_base = format!(
                                            r#"{}
  update{}Model(uuid, obj) {{
    globalThis.__update{}Model(uuid, JSON.stringify(obj));
    return;
  }},"#,
                                            js_base,
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case()
                                        );

                                        js_body = format!(
                                            r#"{}
        global.set(
            "__update{}Model",
            Function::new(ctx.clone(), |ctx: Ctx, uuid: Vec<u8>, val: String| -> Result<()> {{
                let uuid: [u8; 16] = uuid[..].try_into().unwrap();

                let object: ordinary::{} =
                    serde_json::from_str(val.as_str()).expect("failed to deserialize object");

                if let Err(err) = ordinary::update_{}_model(uuid, object) {{
                    ordinary::trace(4, &format!("{{err}}")).unwrap();
                }}

                Ok(())
            }})?
            .with_name("__update{}Model"),
        )?;"#,
                                            js_body,
                                            model_config.name.to_pascal_case(),
                                            input_model_struct_name,
                                            model_config.name,
                                            model_config.name.to_pascal_case(),
                                        );

                                        js_types = format!(
                                            "{}
    function update{}Model(uuid: number[], {}: {}): void;",
                                            js_types,
                                            model_config.name.to_pascal_case(),
                                            model_config.name,
                                            model_config.name.to_pascal_case()
                                        );
                                    }
                                };
                            }
                        }
                        ActionAccessModelOps::Delete => {
                            if !model_delete_has_been_def {
                                ffi_block = format!(
                                    "{}\nfn host_model_delete(ptr: i32, len: i32) -> i32;",
                                    ffi_block
                                );

                                model_delete_has_been_def = true;
                            }

                            if let Some(model_config) = model_map.get(name) {
                                let mut block = String::new();

                                block = format!(
                                    r#"{}
pub fn delete_{}_model(uuid: [u8; 16]) -> Result<(), Box<dyn Error>> {{
    let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);
    let mut req = builder.start_vector();

    req.push({});
    req.push(&uuid[..]);
    
    req.end_vector();

    let mut req = builder.view().to_vec();

    let len = req.len();
    let ptr = req.as_mut_ptr();

    std::mem::forget(req);

    let val = unsafe {{ host_model_update(ptr as i32, len as i32) }};

    if val == 0 {{
        return Err("failed to delete".into());
    }}

    Ok(())
}}"#,
                                    block, model_config.name, model_config.idx,
                                );

                                block_defs.push(block);

                                match action_config.lang {
                                    ActionLang::Rust => {}
                                    ActionLang::JavaScript => {
                                        js_base = format!(
                                            r#"{}
  delete{}Model(uuid) {{
    globalThis.__delete{}Model(uuid);
    return;
  }},"#,
                                            js_base,
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case()
                                        );

                                        js_body = format!(
                                            r#"{}
        global.set(
            "__delete{}Model",
            Function::new(ctx.clone(), |ctx: Ctx, uuid: Vec<u8>| -> Result<()> {{
                let uuid: [u8; 16] = uuid[..].try_into().unwrap();
                if let Err(err) = ordinary::delete_{}_model(uuid) {{
                    ordinary::trace(4, &format!("{{err}}")).unwrap();
                }}
                Ok(())
            }})?
            .with_name("__delete{}Model"),
        )?;"#,
                                            js_body,
                                            model_config.name.to_pascal_case(),
                                            model_config.name,
                                            model_config.name.to_pascal_case(),
                                        );

                                        js_types = format!(
                                            "{}
    function delete{}Model(uuid: number[]): void;",
                                            js_types,
                                            model_config.name.to_pascal_case(),
                                        );
                                    }
                                };
                            }
                        }
                        ActionAccessModelOps::Get => {
                            if let Some(model_config) = model_map.get(name) {
                                if !model_get_has_been_def {
                                    ffi_block = format!(
                                        "{}\nfn host_model_get(ptr: i32, len: i32) -> i64;",
                                        ffi_block
                                    );

                                    model_get_has_been_def = true;
                                }

                                let mut block = String::new();

                                let output_model_struct_name =
                                    format!("Read{}", model_config.name.clone().to_pascal_case());

                                if !defined_models.contains(&output_model_struct_name) {
                                    let (top, nested, external_type_defs) = fields_to_struct(
                                        &action_config,
                                        output_model_struct_name.clone(),
                                        model_config.fields.clone(),
                                        model_map,
                                        true,
                                        true,
                                    );

                                    match action_config.lang {
                                        ActionLang::Rust => {}
                                        ActionLang::JavaScript => {
                                            js_types =
                                                format!("{}\n{}", js_types, external_type_defs)
                                        }
                                    }

                                    defined_models.insert(output_model_struct_name.clone());

                                    block = top;

                                    for nest in nested {
                                        block = format!("{}\n{}", block, nest);
                                    }
                                }

                                block = format!(
                                    r#"{}
pub fn get_{}_model(field_idx: u8, value: Get) -> Result<{}, Box<dyn Error>> {{
    let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);
    let mut req = builder.start_vector();

    req.push({});
    req.push(field_idx);
    req.push(&value.as_bytes());
    
    req.end_vector();

    let mut req = builder.view().to_vec();

    let len = req.len();
    let ptr = req.as_mut_ptr();

    std::mem::forget(req);

    let item = unsafe {{ host_model_get(ptr as i32, len as i32) }};

    if item == 0 {{
        return Err("failed to get".into());
    }}

    let item_ptr = (item >> 32) as i32;
    let item_len = item as i32;

    let item =
        unsafe {{ Vec::from_raw_parts(item_ptr as *mut u8, item_len as usize, item_len as usize) }};

    let args = flexbuffers::Reader::get_root(&item[..])?;

    Ok({})
}}"#,
                                    block,
                                    model_config.name,
                                    output_model_struct_name.clone(),
                                    model_config.idx,
                                    flex_to_struct_fill(
                                        true,
                                        output_model_struct_name.clone(),
                                        model_config.fields.clone(),
                                        model_map,
                                        true,
                                        true,
                                    )?,
                                );

                                block_defs.push(block);

                                match action_config.lang {
                                    ActionLang::Rust => {}
                                    ActionLang::JavaScript => {
                                        js_base = format!(
                                            r#"{}
  get{}Model(fieldIdx, kind, value) {{
    globalThis.__get{}Model(fieldIdx, kind, JSON.stringify(value));
    return globalThis.__get{}ModelResult;
  }},"#,
                                            js_base,
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case()
                                        );

                                        js_body = format!(
                                            r#"{}
        global.set(
            "__get{}Model",
            Function::new(ctx.clone(), |ctx: Ctx, field_idx: u8, kind: u8, value: String| -> Result<()> {{
                let get = match kind {{
                    0 => ordinary::Get::Uuid(serde_json::from_str(&value).unwrap()),
                    1 => ordinary::Get::String(serde_json::from_str(&value).unwrap()),
                    2 => ordinary::Get::Url(serde_json::from_str(&value).unwrap()),
                    _ => {{
                        ordinary::trace(4, &format!("invalid get kind {{}}", kind)).unwrap();
                        ordinary::Get::String("".to_string())
                    }}
                }};

                if let Ok(item) = ordinary::get_{}_model(field_idx, get) {{
                    let global = ctx.globals();

                        if let Ok(item) = rquickjs_serde::to_value(ctx.clone(), item) {{
                            global.set("__get{}ModelResult", item)?;
                        }}
                }}

                Ok(())
            }})?
            .with_name("__get{}Model"),
        )?;"#,
                                            js_body,
                                            model_config.name.to_pascal_case(),
                                            model_config.name,
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case(),
                                        );

                                        js_types = format!(
                                            "{}
    function get{}Model(
        field_idx: number, 
        kind: typeof Get[keyof typeof Get],
        value: any
    ): {};",
                                            js_types,
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case()
                                        );
                                    }
                                };
                            }
                        }
                        ActionAccessModelOps::Query => {
                            if let Some(model_config) = model_map.get(name) {
                                if !model_config
                                    .fields
                                    .iter().any(|f| f.queryable == Some(true))
                                {
                                    return Err(format!(
                                        "no queryable fields for model {} ({})",
                                        model_config.idx, model_config.name
                                    )
                                        .into());
                                }

                                if !model_query_has_been_def {
                                    ffi_block = format!(
                                        "{}\nfn host_model_query(ptr: i32, len: i32) -> i64;",
                                        ffi_block
                                    );

                                    model_query_has_been_def = true;
                                }

                                let mut block = String::new();

                                let output_model_struct_name =
                                    format!("Read{}", model_config.name.clone().to_pascal_case());

                                if !defined_models.contains(&output_model_struct_name) {
                                    let (top, nested, external_type_defs) = fields_to_struct(
                                        &action_config,
                                        output_model_struct_name.clone(),
                                        model_config.fields.clone(),
                                        model_map,
                                        true,
                                        true,
                                    );

                                    match action_config.lang {
                                        ActionLang::Rust => {}
                                        ActionLang::JavaScript => {
                                            js_types =
                                                format!("{}\n{}", js_types, external_type_defs)
                                        }
                                    }

                                    defined_models.insert(output_model_struct_name.clone());

                                    block = top;

                                    for nest in nested {
                                        block = format!("{}\n{}", block, nest);
                                    }
                                }

                                block = format!(
                                    r#"{}
pub fn query_{}_model(field_idx: u8, expression: Expression, value: Query) -> Result<Vec<{}>, Box<dyn Error>> {{
    let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);
    let mut req = builder.start_vector();

    req.push({});
    req.push(field_idx);
    req.push(expression.as_byte());
    req.push(&value.as_bytes());
    
    req.end_vector();

    let mut req = builder.view().to_vec();

    let len = req.len();
    let ptr = req.as_mut_ptr();

    std::mem::forget(req);

    let items = unsafe {{ host_model_query(ptr as i32, len as i32) }};

    if items == 0 {{
        return Err("failed to query".into());
    }}

    let items_ptr = (items >> 32) as i32;
    let items_len = items as i32;

    let items =
        unsafe {{ Vec::from_raw_parts(items_ptr as *mut u8, items_len as usize, items_len as usize) }};

    let root = flexbuffers::Reader::get_root(&items[..])?;

    let out = root.as_vector().iter().map(|args| {{
        {}
    }}).collect::<Vec<{}>>();

    Ok(out)
}}"#,
                                    block,
                                    model_config.name,
                                    output_model_struct_name.clone(),
                                    model_config.idx,
                                    flex_to_struct_fill(
                                        true,
                                        output_model_struct_name.clone(),
                                        model_config.fields.clone(),
                                        model_map,
                                        true,
                                        true,
                                    )?,
                                    output_model_struct_name.clone(),
                                );

                                block_defs.push(block);

                                match action_config.lang {
                                    ActionLang::Rust => {}
                                    ActionLang::JavaScript => {
                                        js_base = format!(
                                            r#"{}
  query{}Model(fieldIdx, expression, kind, value) {{
    globalThis.__query{}Model(fieldIdx, expression, kind, JSON.stringify(value));
    return globalThis.__query{}ModelResult;
  }},"#,
                                            js_base,
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case()
                                        );

                                        js_body = format!(
                                            r#"{}
        global.set(
            "__query{}Model",
            Function::new(ctx.clone(), |ctx: Ctx, field_idx: u8, expression: u8, kind: u8, value: String| -> Result<()> {{
                let exp = match expression {{
                    0 => ordinary::Expression::Gte,
                    1 => ordinary::Expression::Lte,
                    2 => ordinary::Expression::Eq,
                    3 => ordinary::Expression::Gt,
                    4 => ordinary::Expression::Lt,
                    5 => ordinary::Expression::BeginsWith,
                    _ => {{
                        ordinary::trace(4, &format!("invalid expression {{}}", expression))
                            .unwrap();
                        ordinary::Expression::Eq
                    }}
                }};

                let query = match kind {{
                    0 => ordinary::Query::Uuid(serde_json::from_str(&value).unwrap()),
                    1 => ordinary::Query::String(serde_json::from_str(&value).unwrap()),
                    2 => ordinary::Query::Url(serde_json::from_str(&value).unwrap()),
                    3 => ordinary::Query::Bool(serde_json::from_str(&value).unwrap()),
                    4 => ordinary::Query::F32(serde_json::from_str(&value).unwrap()),
                    5 => ordinary::Query::F64(serde_json::from_str(&value).unwrap()),
                    6 => ordinary::Query::U8(serde_json::from_str(&value).unwrap()),
                    7 => ordinary::Query::U16(serde_json::from_str(&value).unwrap()),
                    8 => ordinary::Query::U32(serde_json::from_str(&value).unwrap()),
                    9 => ordinary::Query::U64(serde_json::from_str(&value).unwrap()),
                    10 => ordinary::Query::I8(serde_json::from_str(&value).unwrap()),
                    11 => ordinary::Query::I16(serde_json::from_str(&value).unwrap()),
                    12 => ordinary::Query::I32(serde_json::from_str(&value).unwrap()),
                    13 => ordinary::Query::I64(serde_json::from_str(&value).unwrap()),
                    14 => ordinary::Query::Timestamp(ordinary::TimeUnit::Seconds(
                        serde_json::from_str(&value).unwrap(),
                    )),
                    _ => {{
                        ordinary::trace(4, &format!("invalid query kind {{}}", kind)).unwrap();
                        ordinary::Query::String("".to_string())
                    }}
                }};

                if let Ok(items) = ordinary::query_{}_model(field_idx, exp, query) {{
                    let global = ctx.globals();

                        if let Ok(items) = rquickjs_serde::to_value(ctx.clone(), items) {{
                            global.set("__query{}ModelResult", items)?;
                        }}
                }}

                Ok(())
            }})?
            .with_name("__query{}Model"),
        )?;"#,
                                            js_body,
                                            model_config.name.to_pascal_case(),
                                            model_config.name,
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case(),
                                        );

                                        js_types = format!(
                                            "{}
    function query{}Model(
        field_idx: number, 
        expression: typeof Expression[keyof typeof Expression], 
        kind: typeof Query[keyof typeof Query], 
        value: any
    ): {}[];",
                                            js_types,
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case()
                                        );
                                    }
                                };
                            }
                        }
                        ActionAccessModelOps::Search => {
                            if let Some(model_config) = model_map.get(name) {
                                if !model_config
                                    .fields
                                    .iter().any(|f| f.searchable == Some(true))
                                {
                                    return Err(format!(
                                        "no searchable fields for model {} ({})",
                                        model_config.idx, model_config.name
                                    )
                                        .into());
                                }

                                if !model_search_has_been_def {
                                    ffi_block = format!(
                                        "{}\nfn host_model_search(ptr: i32, len: i32) -> i64;",
                                        ffi_block
                                    );

                                    model_search_has_been_def = true;
                                }

                                let mut block = String::new();

                                let output_model_struct_name =
                                    format!("Read{}", model_config.name.clone().to_pascal_case());

                                if !defined_models.contains(&output_model_struct_name) {
                                    let (top, nested, external_type_defs) = fields_to_struct(
                                        &action_config,
                                        output_model_struct_name.clone(),
                                        model_config.fields.clone(),
                                        model_map,
                                        true,
                                        true,
                                    );

                                    match action_config.lang {
                                        ActionLang::Rust => {}
                                        ActionLang::JavaScript => {
                                            js_types =
                                                format!("{}\n{}", js_types, external_type_defs)
                                        }
                                    }

                                    defined_models.insert(output_model_struct_name.clone());

                                    block = top;

                                    for nest in nested {
                                        block = format!("{}\n{}", block, nest);
                                    }
                                }

                                block = format!(
                                    r#"{}
pub fn search_{}_model(field_idx: u8, value: Search) -> Result<Vec<{}>, Box<dyn Error>> {{
    let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);
    let mut req = builder.start_vector();

    req.push({});
    req.push(field_idx);
    req.push(&value.as_bytes());
    
    req.end_vector();

    let mut req = builder.view().to_vec();

    let len = req.len();
    let ptr = req.as_mut_ptr();

    std::mem::forget(req);

    let items = unsafe {{ host_model_search(ptr as i32, len as i32) }};

    if items == 0 {{
        return Err("failed to search".into());
    }}

    let items_ptr = (items >> 32) as i32;
    let items_len = items as i32;

    let items =
        unsafe {{ Vec::from_raw_parts(items_ptr as *mut u8, items_len as usize, items_len as usize) }};

    let root = flexbuffers::Reader::get_root(&items[..])?;

    let out = root.as_vector().iter().map(|args| {{
        {}
    }}).collect::<Vec<{}>>();

    Ok(out)
}}"#,
                                    block,
                                    model_config.name,
                                    output_model_struct_name.clone(),
                                    model_config.idx,
                                    flex_to_struct_fill(
                                        true,
                                        output_model_struct_name.clone(),
                                        model_config.fields.clone(),
                                        model_map,
                                        true,
                                        true,
                                    )?,
                                    output_model_struct_name.clone(),
                                );

                                block_defs.push(block);

                                match action_config.lang {
                                    ActionLang::Rust => {}
                                    ActionLang::JavaScript => {
                                        js_base = format!(
                                            r#"{}
  search{}Model(fieldIdx, kind, value) {{
    globalThis.__search{}Model(fieldIdx, kind, JSON.stringify(value));
    return globalThis.__search{}ModelResult;
  }},"#,
                                            js_base,
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case()
                                        );

                                        js_body = format!(
                                            r#"{}
        global.set(
            "__search{}Model",
            Function::new(ctx.clone(), |ctx: Ctx, field_idx: u8, kind: u8, value: String| -> Result<()> {{
                let search = match kind {{
                    0 => ordinary::Search::Uuid(serde_json::from_str(&value).unwrap()),
                    1 => ordinary::Search::String(serde_json::from_str(&value).unwrap()),
                    2 => ordinary::Search::Url(serde_json::from_str(&value).unwrap()),
                    3 => ordinary::Search::Bool(serde_json::from_str(&value).unwrap()),
                    4 => ordinary::Search::F32(serde_json::from_str(&value).unwrap()),
                    5 => ordinary::Search::F64(serde_json::from_str(&value).unwrap()),
                    6 => ordinary::Search::U8(serde_json::from_str(&value).unwrap()),
                    7 => ordinary::Search::U16(serde_json::from_str(&value).unwrap()),
                    8 => ordinary::Search::U32(serde_json::from_str(&value).unwrap()),
                    9 => ordinary::Search::U64(serde_json::from_str(&value).unwrap()),
                    10 => ordinary::Search::I8(serde_json::from_str(&value).unwrap()),
                    11 => ordinary::Search::I16(serde_json::from_str(&value).unwrap()),
                    12 => ordinary::Search::I32(serde_json::from_str(&value).unwrap()),
                    13 => ordinary::Search::I64(serde_json::from_str(&value).unwrap()),
                    14 => ordinary::Search::Timestamp(ordinary::TimeUnit::Seconds(
                        serde_json::from_str(&value).unwrap(),
                    )),
                    15 => ordinary::Search::Markdown(serde_json::from_str(&value).unwrap()),
                    16 => ordinary::Search::Json(serde_json::from_str(&value).unwrap()),
                    _ => {{
                        ordinary::trace(4, &format!("invalid search kind {{}}", kind)).unwrap();
                        ordinary::Search::String("".to_string())
                    }}
                }};

                if let Ok(items) = ordinary::search_{}_model(field_idx, search) {{
                    let global = ctx.globals();

                        if let Ok(items) = rquickjs_serde::to_value(ctx.clone(), items) {{
                            global.set("__search{}ModelResult", items)?;
                        }}
                }}

                Ok(())
            }})?
            .with_name("__search{}Model"),
        )?;"#,
                                            js_body,
                                            model_config.name.to_pascal_case(),
                                            model_config.name,
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case(),
                                        );

                                        js_types = format!(
                                            "{}
    function search{}Model(
        field_idx: number, 
        kind: typeof Search[keyof typeof Search],
        value: any
    ): {}[];",
                                            js_types,
                                            model_config.name.to_pascal_case(),
                                            model_config.name.to_pascal_case()
                                        );
                                    }
                                };
                            }
                        }
                    }
                }
            }
            ActionAccessPermission::Content { name } => {
                if defined_content.is_empty() {
                    ffi_block = format!(
                        "{}\nfn host_content_get(ptr: i32, len: i32) -> i64;\nfn host_content_list(ptr: i32, len: i32) -> i64;\nfn host_content_search(ptr: i32, len: i32) -> i64;",
                        ffi_block
                    );
                }

                if let Some(content_def) = content_map.get(name) {
                    let mut block = String::new();

                    if !defined_content.contains(&content_def.name) {
                        let (top, nested, external_type_defs) = fields_to_struct(
                            &action_config,
                            content_def.name.clone(),
                            content_def.fields.clone(),
                            model_map,
                            true,
                            false,
                        );

                        match action_config.lang {
                            ActionLang::Rust => {}
                            ActionLang::JavaScript => {
                                js_types = format!("{}\n{}", js_types, external_type_defs)
                            }
                        }

                        defined_content.insert(content_def.name.clone());

                        block = top;

                        for nest in nested {
                            block = format!("{}\n{}", block, nest);
                        }
                    }

                    block = format!(
                        r#"{}
pub fn list_{}_content() -> Result<Vec<{}>, Box<dyn Error>> {{
    let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);
    let mut req = builder.start_vector();

    req.push({});
    req.end_vector();

    let mut req = builder.view().to_vec();

    let len = req.len();
    let ptr = req.as_mut_ptr();

    std::mem::forget(req);

    let objects = unsafe {{ host_content_list(ptr as i32, len as i32) }};

    if objects == 0 {{
        return Err("failed to get objects".into());
    }}

    let objects_ptr = (objects >> 32) as i32;
    let objects_len = objects as i32;

    let objects =
    unsafe {{ Vec::from_raw_parts(objects_ptr as *mut u8, objects_len as usize, objects_len as usize) }};

    let root = flexbuffers::Reader::get_root(&objects[..])?;

    let out = root.as_vector().iter().map(|obj| {{
        let object = obj.as_vector().iter().map(|v| v.as_u8()).collect::<Vec<u8>>();
        let args = flexbuffers::Reader::get_root(&object[..]).unwrap();

        {}
    }}).collect::<Vec<{}>>();

    Ok(out)
}}

pub fn get_{}_content(field: u8, value: Get) -> Result<{}, Box<dyn Error>> {{
    let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);
    let mut req = builder.start_vector();

    req.push({});
    req.push(&value.as_bytes());
    req.push(field);

    req.end_vector();

    let mut req = builder.view().to_vec();

    let len = req.len();
    let ptr = req.as_mut_ptr();

    std::mem::forget(req);

    let object = unsafe {{ host_content_get(ptr as i32, len as i32) }};

    if object == 0 {{
        return Err("failed to get object".into());
    }}

    let object_ptr = (object >> 32) as i32;
    let object_len = object as i32;

    let object =
    unsafe {{ Vec::from_raw_parts(object_ptr as *mut u8, object_len as usize, object_len as usize) }};

    let args = flexbuffers::Reader::get_root(&object[..])?;

    Ok({})
}}

pub fn search_{}_content(field: u8, value: Search) -> Result<Vec<{}>, Box<dyn Error>> {{
    let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);
    let mut req = builder.start_vector();

    req.push({});
    req.push(field);
    req.push(&value.as_bytes());

    req.end_vector();

    let mut req = builder.view().to_vec();

    let len = req.len();
    let ptr = req.as_mut_ptr();

    std::mem::forget(req);

    let objects = unsafe {{ host_content_search(ptr as i32, len as i32) }};

    if objects == 0 {{
        return Err("failed to get object".into());
    }}

    let objects_ptr = (objects >> 32) as i32;
    let objects_len = objects as i32;

    let objects =
    unsafe {{ Vec::from_raw_parts(objects_ptr as *mut u8, objects_len as usize, objects_len as usize) }};

    let root = flexbuffers::Reader::get_root(&objects[..])?;

    let out = root.as_vector().iter().map(|obj| {{
        let object = obj.as_vector().iter().map(|v| v.as_u8()).collect::<Vec<u8>>();
        let args = flexbuffers::Reader::get_root(&object[..]).unwrap();

        {}
    }}).collect::<Vec<{}>>();

    Ok(out)
}}"#,
                        block,
                        content_def.name,
                        content_def.name.to_pascal_case(),
                        content_def.idx,
                        flex_to_struct_fill(
                            true,
                            content_def.name.clone(),
                            content_def.fields.clone(),
                            model_map,
                            false,
                            false,
                        )?,
                        content_def.name.to_pascal_case(),
                        // split
                        content_def.name,
                        content_def.name.to_pascal_case(),
                        content_def.idx,
                        flex_to_struct_fill(
                            true,
                            content_def.name.clone(),
                            content_def.fields.clone(),
                            model_map,
                            false,
                            false,
                        )?,
                        // split
                        content_def.name,
                        content_def.name.to_pascal_case(),
                        content_def.idx,
                        flex_to_struct_fill(
                            true,
                            content_def.name.clone(),
                            content_def.fields.clone(),
                            model_map,
                            false,
                            false,
                        )?,
                        content_def.name.to_pascal_case()
                    );

                    block_defs.push(block);

                    match action_config.lang {
                        ActionLang::Rust => {}
                        ActionLang::JavaScript => {
                            // todo: search{}Content
                            // todo: list{}Content

                            js_base = format!(
                                r#"{}
  get{}Content(field, kind, value) {{
    globalThis.__get{}Content(field, kind, JSON.stringify(value));
    return globalThis.__get{}ContentResult;
  }},"#,
                                js_base,
                                content_def.name.to_pascal_case(),
                                content_def.name.to_pascal_case(),
                                content_def.name.to_pascal_case()
                            );

                            js_body = format!(
                                r#"{}
        global.set(
            "__get{}Content",
            Function::new(
                ctx.clone(),
                |ctx: Ctx, field: u8, kind: u8, val: String| -> Result<()> {{
                    let get = match kind {{
                        0 => ordinary::Get::Uuid(serde_json::from_str(&val).unwrap()),
                        1 => ordinary::Get::String(serde_json::from_str(&val).unwrap()),
                        2 => ordinary::Get::Url(serde_json::from_str(&val).unwrap()),
                        _ => {{
                            ordinary::trace(4, &format!("invalid get kind {{}}", kind)).unwrap();
                            ordinary::Get::String("".to_string())
                        }}
                    }};

                    if let Ok(obj) = ordinary::get_{}_content(field, get) {{
                        let global = ctx.globals();

                        if let Ok(obj) = rquickjs_serde::to_value(ctx.clone(), obj) {{
                            global.set("__get{}ContentResult", obj)?;
                        }}
                    }}

                    Ok(())
                }},
            )?
            .with_name("__get{}Content"),
        )?;"#,
                                js_body,
                                content_def.name.to_pascal_case(),
                                content_def.name,
                                content_def.name.to_pascal_case(),
                                content_def.name.to_pascal_case()
                            );

                            js_types = format!(
                                "{}
    function get{}Content(
        fieldIdx: number, 
        kind: typeof Get[keyof typeof Get], 
        value: any,
    ): {};",
                                js_types,
                                content_def.name.to_pascal_case(),
                                content_def.name.to_pascal_case()
                            );
                        }
                    };
                }
            }
            ActionAccessPermission::Integration { name } => {
                if !integration_has_been_def {
                    ffi_block = format!(
                        "{}\nfn host_integration(ptr: i32, len: i32) -> i64;",
                        ffi_block
                    );

                    integration_has_been_def = true;
                }

                if let Some(integration_config) = integrations_map.get(name) {
                    let (send_type_name, send_struct_def) = integration_config
                        .send
                        .to_stringified_type_w_lifetime("", "a", use_cow_str);

                    let (send_name, send_builder) =
                        if let Some((struct_name, struct_fields)) = send_struct_def {
                            let (top, nested, external_type_defs) = fields_to_struct_lifetime(
                                &action_config,
                                struct_name.clone(),
                                struct_fields.clone(),
                                model_map,
                                false,
                                false,
                                use_cow_str,
                            );

                            match action_config.lang {
                                ActionLang::Rust => {}
                                ActionLang::JavaScript => {
                                    js_types = format!("{}\n{}", js_types, external_type_defs)
                                }
                            }

                            block_defs.push(top);

                            for nest in nested {
                                block_defs.push(nest);
                            }

                            let fill = format!(
                                "let mut req = builder.start_vector();
                                req.push({});\n{}\n{}",
                                integration_config.idx,
                                struct_to_vec_push(
                                    "req".into(),
                                    "send".to_string(),
                                    struct_fields,
                                    model_map,
                                    true,
                                    use_cow_str,
                                ),
                                "req.end_vector();"
                            );

                            (struct_name, fill)
                        } else {
                            let fill = format!(
                                "let mut req = builder.start_vector();
                                req.push({});\n{}\n{}",
                                integration_config.idx,
                                match &integration_config.send {
                                    Kind::Uuid => "req.push(&send[..]);".to_string(),
                                    _ => "req.push(send);".to_string(),
                                },
                                "req.end_vector();"
                            );

                            (send_type_name, fill)
                        };

                    let (recv_type_name, recv_struct_def) =
                        integration_config.recv.to_stringified_type("");

                    let (recv_name, recv_accessor) =
                        if let Some((struct_name, struct_fields)) = recv_struct_def {
                            let (top, nested, external_type_defs) = fields_to_struct(
                                &action_config,
                                struct_name.clone(),
                                struct_fields.clone(),
                                model_map,
                                false,
                                false,
                            );

                            match action_config.lang {
                                ActionLang::Rust => {}
                                ActionLang::JavaScript => {
                                    js_types = format!("{}\n{}", js_types, external_type_defs)
                                }
                            }

                            block_defs.push(top);

                            for nest in nested {
                                block_defs.push(nest);
                            }

                            let fill = flex_to_struct_fill(
                                true,
                                struct_name.clone(),
                                struct_fields,
                                model_map,
                                false,
                                false,
                            )?;

                            (struct_name, fill)
                        } else {
                            (
                                recv_type_name,
                                format!(
                                    "args{}.into()",
                                    integration_config.recv.to_flexbuffer_accessor().unwrap()
                                ),
                            )
                        };

                    let block = format!(
                        r#"pub fn {}<'a>(send: {}) -> Result<{}, Box<dyn Error>> {{
    let mut builder = flexbuffers::Builder::new(flexbuffers::BuilderOptions::SHARE_NONE);

    {}

    let mut send = builder.view().to_vec();


    let send_len = send.len();
    let send_ptr = send.as_mut_ptr();

    std::mem::forget(send);
    let res = unsafe {{ host_integration(send_ptr as i32, send_len as i32) }};

    if res == 0 {{
        return Err("failed to call integration".into());
    }}

    let res_ptr = (res >> 32) as i32;
    let res_len = res as i32;

    let res =
    unsafe {{ Vec::from_raw_parts(res_ptr as *mut u8, res_len as usize, res_len as usize) }};

    let root = flexbuffers::Reader::get_root(&res[..])?;
                
    let args = root.as_vector().idx(0);

    Ok({})
}}"#,
                        integration_config.name, send_name, recv_name, send_builder, recv_accessor
                    );

                    block_defs.push(block);

                    match action_config.lang {
                        ActionLang::Rust => {}
                        ActionLang::JavaScript => {
                            js_base = format!(
                                r#"{}
  {}(send) {{
    globalThis.__{}(JSON.stringify(send));
    return globalThis.__{}Result;
  }},"#,
                                js_base,
                                integration_config.name.to_lower_camel_case(),
                                integration_config.name.to_lower_camel_case(),
                                integration_config.name.to_lower_camel_case()
                            );

                            js_body = format!(
                                r#"{}
        global.set(
            "__{}",
            Function::new(
                ctx.clone(),
                |ctx: Ctx, send: String| -> Result<()> {{
                    let send: ordinary::{} =
                        serde_json::from_str(send.as_str()).expect("failed to deserialize send");

                    if let Ok(recv) = ordinary::{}(send) {{
                        let global = ctx.globals();

                        if let Ok(recv) = rquickjs_serde::to_value(ctx.clone(), recv) {{
                            global.set("__{}Result", recv)?;
                        }}
                    }}

                    Ok(())
                }},
            )?
            .with_name("__{}"),
        )?;"#,
                                js_body,
                                integration_config.name.to_lower_camel_case(),
                                send_name,
                                integration_config.name,
                                integration_config.name.to_lower_camel_case(),
                                integration_config.name.to_lower_camel_case()
                            );
                        }
                    };
                }
            }
            ActionAccessPermission::Action { name: _ } => {}
        }
    }

    // action_config.returns

    ffi_block = format!("{}\n}}", ffi_block);

    let const_dec = format!(r#"pub const DOMAIN: &'static str = "{}";"#, domain);

    let mut generated_file = format!(
        "{}\n{}\n{}\n\n",
        ffi_block,
        BINDINGS,
        const_dec
    );

    for block_def in block_defs {
        generated_file = format!("{}{}", generated_file, block_def);
    }

    let extras = match action_config.lang {
        ActionLang::Rust => None,
        ActionLang::JavaScript => {
            js_base = format!(
                r##"{}
    Domain: "{}"
}};"#,
    include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/index.js")),
    "action.main(globalThis.__params, globalThis.__claims)"
);
"##,
                js_base, domain
            );

            js_body = format!(
                r#"{}
        match ctx.eval::<Value<'_>, _>(JS) {{
            Ok(ret) => {{
                match rquickjs_serde::from_value::<{}>(ret) {{
                    Ok(out) => {{
                        if let Err(err) = ordinary::send_out(out) {{
                            ordinary::trace(4, &err.to_string()).unwrap();
                        }}
                    }}
                    Err(err) => {{
                        ordinary::trace(4, &err.to_string()).unwrap();
                    }}
                }}
            }}
            Err(err) => {{
                ordinary::trace(4, &err.to_string()).unwrap();
            }} 
        }}

        Ok(())
    }})?;

    Ok(())
}}"#,
                js_body, returns_name
            );

            let type_defs = format!(
                "{} 
}}",
                js_types
            );

            let main_rs = format!("{}\n{}", js_base, js_body);
            let cargo_toml = format!(
                r#"{}
ordinary = {{ path = "../../.ordinary/gen/actions/{}" }}"#,
                BASE_JAVASCRIPT_TOML, action_config.name
            );

            Some((main_rs, cargo_toml, type_defs))
        }
    };

    Ok((generated_file, extras))
}
