/*
 * Copyright 2022, The Cozo Project Authors.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
 * If a copy of the MPL was not distributed with this file,
 * You can obtain one at https://mozilla.org/MPL/2.0/.
 */
use std::collections::BTreeMap;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::{Arc, Mutex};

use crossbeam::channel::Sender;
use lazy_static::lazy_static;
use miette::{miette, Result};
use neon::prelude::*;
use neon::types::buffer::TypedArray;
use serde_json::json;

use cozo::*;

fn rows2js<'a>(cx: &mut impl Context<'a>, rows: &[Vec<DataValue>]) -> JsResult<'a, JsArray> {
    let coll = cx.empty_array();
    for (j, row) in rows.iter().enumerate() {
        let cur = cx.empty_array();
        for (i, el) in row.iter().enumerate() {
            let el = value2js(cx, el)?;
            cur.set(cx, i as u32, el)?;
        }
        coll.set(cx, j as u32, cur)?;
    }
    Ok(coll)
}

fn named_rows2js<'a>(cx: &mut impl Context<'a>, nr: &NamedRows) -> JsResult<'a, JsObject> {
    let ret = cx.empty_object();
    if let Some(rows) = &nr.next {
        let converted = named_rows2js(cx, rows)?;
        ret.set(cx, "next", converted)?;
    };
    let headers = cx.empty_array();
    for (i, header) in nr.headers.iter().enumerate() {
        let converted = cx.string(header);
        headers.set(cx, i as u32, converted)?;
    }
    ret.set(cx, "headers", headers)?;
    let rows = rows2js(cx, &nr.rows)?;
    ret.set(cx, "rows", rows)?;
    Ok(ret)
}

fn js2value<'a>(
    cx: &mut impl Context<'a>,
    val: Handle<'a, JsValue>,
    coll: &mut DataValue,
) -> JsResult<'a, JsUndefined> {
    if val.downcast::<JsNull, _>(cx).is_ok() {
        *coll = DataValue::Null;
    } else if let Ok(n) = val.downcast::<JsNumber, _>(cx) {
        let n = n.value(cx);
        *coll = DataValue::from(n);
    } else if let Ok(b) = val.downcast::<JsBoolean, _>(cx) {
        let b = b.value(cx);
        *coll = DataValue::from(b);
    } else if val.downcast::<JsUndefined, _>(cx).is_ok() {
        *coll = DataValue::Null;
    } else if let Ok(s) = val.downcast::<JsString, _>(cx) {
        let s = s.value(cx);
        *coll = DataValue::Str(s.into());
    } else if let Ok(l) = val.downcast::<JsArray, _>(cx) {
        let n = l.len(cx);
        let mut ret = Vec::with_capacity(n as usize);
        for i in 0..n {
            let v: Handle<JsValue> = l.get(cx, i)?;
            let mut target = DataValue::Bot;
            js2value(cx, v, &mut target)?;
            ret.push(target);
        }
        *coll = DataValue::List(ret);
    } else if let Ok(b) = val.downcast::<JsBuffer, _>(cx) {
        let d = b.as_slice(cx);
        *coll = DataValue::Bytes(d.to_vec());
    } else if let Ok(obj) = val.downcast::<JsObject, _>(cx) {
        let names = obj.get_own_property_names(cx)?;
        let mut coll_inner = serde_json::Map::default();
        for i in 0..names.len(cx) {
            let name = names.get::<JsString, _, _>(cx, i)?.value(cx);
            let v = obj.get::<JsValue, _, _>(cx, &*name)?;
            let mut target = DataValue::Bot;
            js2value(cx, v, &mut target)?;
            coll_inner.insert(name, serde_json::Value::from(target));
        }
        *coll = DataValue::Json(JsonData(json!(coll_inner)));
    } else {
        let err = cx.string("Javascript value cannot be converted.");
        return cx.throw(err);
    }
    Ok(cx.undefined())
}

fn json2js<'a>(cx: &mut impl Context<'a>, val: &serde_json::Value) -> JsResult<'a, JsValue> {
    Ok(match val {
        serde_json::Value::Null => cx.null().as_value(cx),
        serde_json::Value::Bool(b) => cx.boolean(*b).as_value(cx),
        serde_json::Value::Number(n) => {
            if let Some(i) = n.as_i64() {
                cx.number(i as f64).as_value(cx)
            } else if let Some(f) = n.as_f64() {
                cx.number(f).as_value(cx)
            } else {
                cx.undefined().as_value(cx)
            }
        }
        serde_json::Value::String(s) => cx.string(s).as_value(cx),
        serde_json::Value::Array(l) => {
            let target_l = cx.empty_array();
            for (i, el) in l.iter().enumerate() {
                let el = json2js(cx, el)?;
                target_l.set(cx, i as u32, el)?;
            }
            target_l.as_value(cx)
        }
        serde_json::Value::Object(m) => {
            let target_m = cx.empty_object();
            for (k, v) in m.iter() {
                let k = cx.string(k);
                let v = json2js(cx, v)?;
                target_m.set(cx, k, v)?;
            }
            target_m.as_value(cx)
        }
    })
}

fn value2js<'a>(cx: &mut impl Context<'a>, val: &DataValue) -> JsResult<'a, JsValue> {
    Ok(match val {
        DataValue::Null => cx.null().as_value(cx),
        DataValue::Bool(b) => cx.boolean(*b).as_value(cx),
        DataValue::Num(n) => match n {
            Num::Int(i) => cx.number(*i as f64).as_value(cx),
            Num::Float(f) => cx.number(*f).as_value(cx),
        },
        DataValue::Str(s) => cx.string(s).as_value(cx),
        DataValue::Bytes(b) => {
            let b = b.clone();
            JsBuffer::external(cx, b).as_value(cx)
        }
        DataValue::Uuid(uuid) => cx.string(uuid.0.to_string()).as_value(cx),
        DataValue::Regex(rx) => cx.string(rx.0.to_string()).as_value(cx),
        DataValue::List(l) => {
            let target_l = cx.empty_array();
            for (i, el) in l.iter().enumerate() {
                let el = value2js(cx, el)?;
                target_l.set(cx, i as u32, el)?;
            }
            target_l.as_value(cx)
        }
        DataValue::Set(l) => {
            let target_l = cx.empty_array();
            for (i, el) in l.iter().enumerate() {
                let el = value2js(cx, el)?;
                target_l.set(cx, i as u32, el)?;
            }
            target_l.as_value(cx)
        }
        DataValue::Validity(vld) => {
            let target_l = cx.empty_array();
            let ts = cx.number(vld.timestamp.0 .0 as f64);
            target_l.set(cx, 0, ts)?;
            let a = cx.boolean(vld.is_assert.0);
            target_l.set(cx, 1, a)?;
            target_l.as_value(cx)
        }
        DataValue::Bot => cx.undefined().as_value(cx),
        DataValue::Vec(v) => {
            let target_l = cx.empty_array();
            match v {
                Vector::F32(a) => {
                    for (i, el) in a.iter().enumerate() {
                        let el = cx.number(*el as f64);
                        target_l.set(cx, i as u32, el)?;
                    }
                }
                Vector::F64(a) => {
                    for (i, el) in a.iter().enumerate() {
                        let el = cx.number(*el);
                        target_l.set(cx, i as u32, el)?;
                    }
                }
            }
            target_l.as_value(cx)
        }
        DataValue::Json(JsonData(j)) => json2js(cx, j)?,
    })
}

fn js2params<'a>(
    cx: &mut impl Context<'a>,
    js_params: Handle<'a, JsObject>,
    collector: &mut BTreeMap<String, DataValue>,
) -> JsResult<'a, JsUndefined> {
    let keys = js_params.get_own_property_names(cx)?;
    let n_keys = keys.len(cx);
    for i in 0..n_keys {
        let key: Handle<JsString> = keys.get(cx, i)?;
        let key_str = key.value(cx);
        let val: Handle<JsValue> = js_params.get(cx, key)?;
        let mut value = DataValue::Bot;
        js2value(cx, val, &mut value)?;
        collector.insert(key_str, value);
    }
    Ok(cx.undefined())
}

fn js2rows<'a>(
    cx: &mut impl Context<'a>,
    rows: Handle<'a, JsArray>,
    collector: &mut Vec<Vec<DataValue>>,
) -> JsResult<'a, JsUndefined> {
    let n_rows = rows.len(cx);
    collector.reserve(n_rows as usize);
    for i in 0..n_rows {
        let row = rows.get::<JsArray, _, _>(cx, i)?;
        let n_cols = row.len(cx);
        let mut ret_row = Vec::with_capacity(n_cols as usize);
        for j in 0..n_cols {
            let col = row.get::<JsValue, _, _>(cx, j)?;
            let mut val = DataValue::Bot;
            js2value(cx, col, &mut val)?;
            ret_row.push(val);
        }
        collector.push(ret_row);
    }
    Ok(cx.undefined())
}

fn js2stored<'a>(
    cx: &mut impl Context<'a>,
    named_rows: Handle<'a, JsObject>,
    collector: &mut NamedRows,
) -> JsResult<'a, JsUndefined> {
    let headers_js = named_rows.get::<JsArray, _, _>(cx, "headers")?;
    let l = headers_js.len(cx);
    let mut headers = Vec::with_capacity(l as usize);
    for i in 0..l {
        let v = headers_js.get::<JsString, _, _>(cx, i)?.value(cx);
        headers.push(v);
    }
    let rows_js = named_rows.get::<JsArray, _, _>(cx, "rows")?;
    let mut rows = vec![];
    js2rows(cx, rows_js, &mut rows)?;
    collector.headers = headers;
    collector.rows = rows;
    Ok(cx.undefined())
}

fn params2js<'a>(
    cx: &mut impl Context<'a>,
    params: &BTreeMap<String, DataValue>,
) -> JsResult<'a, JsObject> {
    let obj = cx.empty_object();
    for (k, v) in params {
        let val = value2js(cx, v)?;
        obj.set(cx, k as &str, val)?;
    }
    Ok(obj)
}

#[derive(Default)]
struct Handles {
    nxt_db_id: AtomicU32,
    dbs: Mutex<BTreeMap<u32, DbInstance>>,
    cb_idx: AtomicU32,
    current_cbs: Mutex<BTreeMap<u32, Sender<Result<NamedRows>>>>,
    nxt_tx_id: AtomicU32,
    txs: Mutex<BTreeMap<u32, Arc<MultiTransaction>>>,
}

lazy_static! {
    static ref HANDLES: Handles = Handles::default();
}

fn open_db(mut cx: FunctionContext) -> JsResult<JsNumber> {
    let engine = cx.argument::<JsString>(0)?.value(&mut cx);
    let path = cx.argument::<JsString>(1)?.value(&mut cx);
    let options = cx.argument::<JsString>(2)?.value(&mut cx);
    match DbInstance::new(&engine, path, &options) {
        Ok(db) => {
            let id = HANDLES.nxt_db_id.fetch_add(1, Ordering::AcqRel);
            let mut dbs = HANDLES.dbs.lock().unwrap();
            dbs.insert(id, db);
            Ok(cx.number(id))
        }
        Err(err) => {
            let s = cx.string(format!("{err:?}"));
            cx.throw(s)
        }
    }
}

fn close_db(mut cx: FunctionContext) -> JsResult<JsBoolean> {
    let id = cx.argument::<JsNumber>(0)?.value(&mut cx) as u32;
    let db = {
        let mut dbs = HANDLES.dbs.lock().unwrap();
        dbs.remove(&id)
    };
    Ok(cx.boolean(db.is_some()))
}

macro_rules! get_db {
    ($cx:expr) => {{
        let id = $cx.argument::(0)?.value(&mut $cx) as u32;
        let db = {
            let db_ref = {
                let dbs = HANDLES.dbs.lock().unwrap();
                dbs.get(&id).cloned()
            };
            match db_ref {
                None => {
                    let s = $cx.string("database already closed");
                    $cx.throw(s)?
                }
                Some(db) => db,
            }
        };
        db
    }};
}

macro_rules! get_tx {
    ($cx:expr) => {{
        let id = $cx.argument::(0)?.value(&mut $cx) as u32;
        let tx = {
            let tx_ref = {
                let txs = HANDLES.txs.lock().unwrap();
                txs.get(&id).cloned()
            };
            match tx_ref {
                None => {
                    let s = $cx.string("transaction closed");
                    $cx.throw(s)?
                }
                Some(tx) => tx,
            }
        };
        tx
    }};
}

macro_rules! remove_tx {
    ($cx:expr) => {{
        let id = $cx.argument::(0)?.value(&mut $cx) as u32;
        let tx = {
            let tx_ref = {
                let mut txs = HANDLES.txs.lock().unwrap();
                txs.remove(&id)
            };
            match tx_ref {
                None => {
                    let s = $cx.string("transaction closed");
                    $cx.throw(s)?
                }
                Some(tx) => tx,
            }
        };
        tx
    }};
}

fn multi_transact(mut cx: FunctionContext) -> JsResult<JsNumber> {
    let db = get_db!(cx);
    let write = cx.argument::<JsBoolean>(1)?.value(&mut cx);
    let tx = db.multi_transaction(write);
    let id = HANDLES.nxt_tx_id.fetch_add(1, Ordering::AcqRel);
    HANDLES.txs.lock().unwrap().insert(id, Arc::new(tx));
    Ok(cx.number(id))
}

fn abort_tx(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    let tx = remove_tx!(cx);
    match tx.abort() {
        Ok(_) => Ok(cx.undefined()),
        Err(err) => {
            let msg = cx.string(err.to_string());
            cx.throw(msg)
        }
    }
}

fn commit_tx(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    let tx = remove_tx!(cx);
    match tx.commit() {
        Ok(_) => Ok(cx.undefined()),
        Err(err) => {
            let msg = cx.string(err.to_string());
            cx.throw(msg)
        }
    }
}

fn query_db(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    let db = get_db!(cx);
    let query = cx.argument::<JsString>(1)?.value(&mut cx);
    let params_js = cx.argument::<JsObject>(2)?;
    let mut params = BTreeMap::new();
    js2params(&mut cx, params_js, &mut params)?;

    let callback = cx.argument::<JsFunction>(3)?.root(&mut cx);
    let immutable = cx.argument::<JsBoolean>(4)?.value(&mut cx);

    let channel = cx.channel();

    rayon::spawn(move || {
        let result = db.run_script(
            &query,
            params,
            if immutable {
                ScriptMutability::Immutable
            } else {
                ScriptMutability::Mutable
            },
        );
        channel.send(move |mut cx| {
            let callback = callback.into_inner(&mut cx);
            let this = cx.undefined();
            match result {
                Ok(nr) => {
                    let js_vals = named_rows2js(&mut cx, &nr)?.as_value(&mut cx);
                    let err = cx.undefined().as_value(&mut cx);
                    callback.call(&mut cx, this, vec![err, js_vals])?;
                }
                Err(err) => {
                    let reports = format_error_as_json(err, Some(&query)).to_string();
                    let err = cx.string(&reports).as_value(&mut cx);
                    callback.call(&mut cx, this, vec![err])?;
                }
            }
            Ok(())
        });
    });

    Ok(cx.undefined())
}

fn query_tx(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    let tx = get_tx!(cx);
    let query = cx.argument::<JsString>(1)?.value(&mut cx);
    let params_js = cx.argument::<JsObject>(2)?;
    let mut params = BTreeMap::new();
    js2params(&mut cx, params_js, &mut params)?;

    let callback = cx.argument::<JsFunction>(3)?.root(&mut cx);

    let channel = cx.channel();
    match tx
        .sender
        .send(TransactionPayload::Query((query.clone(), params)))
    {
        Ok(_) => {
            rayon::spawn(move || {
                let result = tx.receiver.recv();
                channel.send(move |mut cx| {
                    let callback = callback.into_inner(&mut cx);
                    let this = cx.undefined();
                    match result {
                        Ok(Ok(nr)) => {
                            let js_vals = named_rows2js(&mut cx, &nr)?.as_value(&mut cx);
                            let err = cx.undefined().as_value(&mut cx);
                            callback.call(&mut cx, this, vec![err, js_vals])?;
                        }
                        Ok(Err(err)) => {
                            let reports = format_error_as_json(err, Some(&query)).to_string();
                            let err = cx.string(&reports).as_value(&mut cx);
                            callback.call(&mut cx, this, vec![err])?;
                        }
                        Err(err) => {
                            let err = cx.string(err.to_string()).as_value(&mut cx);
                            callback.call(&mut cx, this, vec![err])?;
                        }
                    }
                    Ok(())
                });
            });

            Ok(cx.undefined())
        }
        Err(err) => {
            let msg = cx.string(err.to_string());
            cx.throw(msg)
        }
    }
}

fn backup_db(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    let db = get_db!(cx);
    let path = cx.argument::<JsString>(1)?.value(&mut cx);
    let callback = cx.argument::<JsFunction>(2)?.root(&mut cx);
    let channel = cx.channel();

    rayon::spawn(move || {
        let result = db.backup_db(&path);
        channel.send(move |mut cx| {
            let callback = callback.into_inner(&mut cx);
            let this = cx.undefined();
            if let Err(msg) = result {
                let reports = format_error_as_json(msg, None).to_string();
                let err = cx.string(&reports).as_value(&mut cx);
                callback.call(&mut cx, this, vec![err])?;
            } else {
                callback.call(&mut cx, this, vec![])?;
            }

            Ok(())
        });
    });

    Ok(cx.undefined())
}

fn restore_db(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    let db = get_db!(cx);
    let path = cx.argument::<JsString>(1)?.value(&mut cx);
    let callback = cx.argument::<JsFunction>(2)?.root(&mut cx);
    let channel = cx.channel();

    rayon::spawn(move || {
        let result = db.restore_backup(&path);
        channel.send(move |mut cx| {
            let callback = callback.into_inner(&mut cx);
            let this = cx.undefined();
            if let Err(msg) = result {
                let reports = format_error_as_json(msg, None).to_string();
                let err = cx.string(&reports).as_value(&mut cx);
                callback.call(&mut cx, this, vec![err])?;
            } else {
                callback.call(&mut cx, this, vec![])?;
            }
            Ok(())
        });
    });

    Ok(cx.undefined())
}

fn export_relations(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    let db = get_db!(cx);
    let rels = cx.argument::<JsArray>(1)?;
    let mut relations = vec![];
    for i in 0..rels.len(&mut cx) {
        let r = rels.get::<JsString, _, _>(&mut cx, i)?.value(&mut cx);
        relations.push(r);
    }
    let callback = cx.argument::<JsFunction>(2)?.root(&mut cx);
    let channel = cx.channel();

    rayon::spawn(move || {
        let result = db.export_relations(relations.iter());
        channel.send(move |mut cx| {
            let callback = callback.into_inner(&mut cx);
            let this = cx.undefined();
            match result {
                Ok(ret) => {
                    let u = cx.undefined().as_value(&mut cx);
                    let data = cx.empty_object();
                    for (k, v) in ret {
                        let nv = named_rows2js(&mut cx, &v)?;
                        data.set(&mut cx, &k as &str, nv)?;
                    }
                    let data = data.as_value(&mut cx);
                    callback.call(&mut cx, this, vec![u, data])?;
                }
                Err(msg) => {
                    let reports = format_error_as_json(msg, None).to_string();
                    let err = cx.string(&reports).as_value(&mut cx);
                    callback.call(&mut cx, this, vec![err])?;
                }
            }
            Ok(())
        });
    });

    Ok(cx.undefined())
}

fn import_relations(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    let db = get_db!(cx);
    let data = cx.argument::<JsObject>(1)?;
    let callback = cx.argument::<JsFunction>(2)?.root(&mut cx);
    let channel = cx.channel();
    let mut rels = BTreeMap::new();
    let names = data.get_own_property_names(&mut cx)?;
    for name in names.to_vec(&mut cx)? {
        let name = name
            .downcast_or_throw::<JsString, _>(&mut cx)?
            .value(&mut cx);
        let val = data.get::<JsObject, _, _>(&mut cx, &name as &str)?;
        let mut nr = NamedRows::default();
        js2stored(&mut cx, val, &mut nr)?;
        rels.insert(name, nr);
    }

    rayon::spawn(move || {
        let result = db.import_relations(rels);
        channel.send(move |mut cx| {
            let callback = callback.into_inner(&mut cx);
            let this = cx.undefined();
            if let Err(msg) = result {
                let reports = format_error_as_json(msg, None).to_string();
                let err = cx.string(&reports).as_value(&mut cx);
                callback.call(&mut cx, this, vec![err])?;
            } else {
                callback.call(&mut cx, this, vec![])?;
            }

            Ok(())
        });
    });

    Ok(cx.undefined())
}

fn import_from_backup(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    let db = get_db!(cx);
    let path = cx.argument::<JsString>(1)?.value(&mut cx);
    let rels = cx.argument::<JsArray>(2)?;
    let mut relations = vec![];
    for i in 0..rels.len(&mut cx) {
        let r = rels.get::<JsString, _, _>(&mut cx, i)?.value(&mut cx);
        relations.push(r);
    }

    let callback = cx.argument::<JsFunction>(3)?.root(&mut cx);
    let channel = cx.channel();

    rayon::spawn(move || {
        let result = db.import_from_backup(path, &relations);
        channel.send(move |mut cx| {
            let callback = callback.into_inner(&mut cx);
            let this = cx.undefined();
            if let Err(msg) = result {
                let reports = format_error_as_json(msg, None).to_string();
                let err = cx.string(&reports).as_value(&mut cx);
                callback.call(&mut cx, this, vec![err])?;
            } else {
                callback.call(&mut cx, this, vec![])?;
            }

            Ok(())
        });
    });

    Ok(cx.undefined())
}

fn register_callback(mut cx: FunctionContext) -> JsResult<JsNumber> {
    let db = get_db!(cx);
    let name = cx.argument::<JsString>(1)?.value(&mut cx);
    let capacity = cx.argument::<JsNumber>(3)?.value(&mut cx);
    let capacity = if capacity < 0. {
        None
    } else {
        Some(capacity as usize)
    };
    let callback = Arc::new(cx.argument::<JsFunction>(2)?.root(&mut cx));
    let channel = cx.channel();

    let (rid, recv) = db.register_callback(&name, capacity);
    rayon::spawn(move || {
        for (op, new, old) in recv {
            let cb = callback.clone();
            channel.send(move |mut cx| {
                let callback = cb.to_inner(&mut cx);
                let op = cx.string(op.as_str()).as_value(&mut cx);
                let new = rows2js(&mut cx, &new.rows)?.as_value(&mut cx);
                let old = rows2js(&mut cx, &old.rows)?.as_value(&mut cx);
                let this = cx.undefined();

                callback.call(&mut cx, this, vec![op, new, old])?;
                Ok(())
            });
        }
    });
    Ok(cx.number(rid))
}

fn unregister_callback(mut cx: FunctionContext) -> JsResult<JsBoolean> {
    let db = get_db!(cx);
    let id = cx.argument::<JsNumber>(1)?.value(&mut cx) as u32;
    let removed = db.unregister_callback(id);
    Ok(cx.boolean(removed))
}

fn register_named_rule(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    let db = get_db!(cx);
    let name = cx.argument::<JsString>(1)?.value(&mut cx);
    let arity = cx.argument::<JsNumber>(2)?.value(&mut cx) as usize;
    let callback = Arc::new(cx.argument::<JsFunction>(3)?.root(&mut cx));
    let channel = cx.channel();
    let (rule_impl, recv) = SimpleFixedRule::rule_with_channel(arity);
    if let Err(err) = db.register_fixed_rule(name, rule_impl) {
        let msg = cx.string(err.to_string());
        return cx.throw(msg);
    }
    rayon::spawn(move || {
        for (inputs, options, sender) in recv {
            let id = HANDLES.cb_idx.fetch_add(1, Ordering::AcqRel);
            {
                HANDLES.current_cbs.lock().unwrap().insert(id, sender);
            }
            let cb = callback.clone();
            channel.send(move |mut cx| {
                let callback = cb.to_inner(&mut cx);
                let inputs_js = cx.empty_array();
                for (i, input) in inputs.into_iter().enumerate() {
                    let input_js = rows2js(&mut cx, &input.rows)?;
                    inputs_js.set(&mut cx, i as u32, input_js)?;
                }
                let inputs_js = inputs_js.as_value(&mut cx);
                let options_js = params2js(&mut cx, &options)?.as_value(&mut cx);
                let this = cx.undefined();
                let ret_id = cx.number(id).as_value(&mut cx);
                callback.call(&mut cx, this, vec![ret_id, inputs_js, options_js])?;

                Ok(())
            });
        }
    });

    Ok(cx.undefined())
}

fn respond_to_named_rule_invocation(mut cx: FunctionContext) -> JsResult<JsUndefined> {
    let ret_id = cx.argument::<JsNumber>(0)?.value(&mut cx) as u32;
    let sender = {
        match HANDLES.current_cbs.lock().unwrap().remove(&ret_id) {
            None => {
                let msg = cx.string("fixed rule invocation sender should only be used once");
                return cx.throw(msg);
            }
            Some(s) => s,
        }
    };

    let send_err = |err| {
        let _ = sender.send(Err(miette!("Javascript fixed rule failed")));
        err
    };

    let payload = cx.argument::<JsValue>(1)?;
    if let Ok(msg) = payload.downcast::<JsString, _>(&mut cx) {
        let _ = sender.send(Err(miette!(msg.value(&mut cx))));
        return Ok(cx.undefined());
    }

    let data = payload.downcast_or_throw(&mut cx).map_err(send_err)?;
    let mut rows = vec![];
    js2rows(&mut cx, data, &mut rows).map_err(send_err)?;
    let nr = NamedRows::new(vec![], rows);
    if let Err(err) = sender.send(Ok(nr)) {
        let msg = err.to_string();
        let msg = cx.string(msg);
        return cx.throw(msg);
    }
    Ok(cx.undefined())
}

fn unregister_named_rule(mut cx: FunctionContext) -> JsResult<JsBoolean> {
    let db = get_db!(cx);
    let name = cx.argument::<JsString>(1)?.value(&mut cx);
    let removed = match db.unregister_fixed_rule(&name) {
        Ok(b) => b,
        Err(msg) => {
            let msg = cx.string(msg.to_string());
            return cx.throw(msg);
        }
    };
    Ok(cx.boolean(removed))
}

#[neon::main]
fn main(mut cx: ModuleContext) -> NeonResult<()> {
    cx.export_function("open_db", open_db)?;
    cx.export_function("close_db", close_db)?;
    cx.export_function("query_db", query_db)?;
    cx.export_function("backup_db", backup_db)?;
    cx.export_function("restore_db", restore_db)?;
    cx.export_function("export_relations", export_relations)?;
    cx.export_function("import_relations", import_relations)?;
    cx.export_function("import_from_backup", import_from_backup)?;
    cx.export_function("register_callback", register_callback)?;
    cx.export_function("unregister_callback", unregister_callback)?;
    cx.export_function("register_named_rule", register_named_rule)?;
    cx.export_function(
        "respond_to_named_rule_invocation",
        respond_to_named_rule_invocation,
    )?;
    cx.export_function("unregister_named_rule", unregister_named_rule)?;
    cx.export_function("abort_tx", abort_tx)?;
    cx.export_function("commit_tx", commit_tx)?;
    cx.export_function("multi_transact", multi_transact)?;
    cx.export_function("query_tx", query_tx)?;
    Ok(())
}

Homonyms

warriors/trisha/wgpu/lib.rs
soft3/glia/run/lib.rs
soft3/mir/src/lib.rs
soft3/foculus/src/lib.rs
cyb/optica/src/lib.rs
soft3/strata/src/lib.rs
warriors/trisha/rs/lib.rs
cyb/shell/src/lib.rs
cyb/core/src/lib.rs
soft3/glia/import/lib.rs
warriors/trisha/honeycrisp/lib.rs
neural/trident/src/lib.rs
soft3/crate/src/lib.rs
cyb/honeycrisp/src/lib.rs
cyb/prysm/rs/lib.rs
soft3/lens/src/lib.rs
soft3/tru/rs/lib.rs
soft3/cybergraph/src/lib.rs
soft3/nox/rs/lib.rs
soft3/mudra/src/lib.rs
soft3/radio/cyber-bao/src/lib.rs
neural/rs/dialect/src/lib.rs
soft3/lens/assayer/src/lib.rs
cyb/honeycrisp/aruminium/src/lib.rs
soft3/lens/brakedown/src/lib.rs
soft3/bbg/rs/src/lib.rs
neural/rs/sigil/src/lib.rs
soft3/radio/iroh-willow/src/lib.rs
cyb/crates/cyb/src/lib.rs
neural/rs/link/src/lib.rs
cyb/honeycrisp/unimem/src/lib.rs
soft3/lens/porphyry/src/lib.rs
soft3/radio/iroh-docs/src/lib.rs
soft3/radio/iroh-car/src/lib.rs
soft3/radio/iroh-dns-server/src/lib.rs
soft3/radio/iroh/src/lib.rs
cyb/crates/cyb-reserve/src/lib.rs
soft3/strata/ext/src/lib.rs
soft3/radio/iroh-relay/src/lib.rs
neural/rune/rs/parse/lib.rs
neural/rs/codegen/src/lib.rs
soft3/radio/iroh-blobs/src/lib.rs
soft3/zheng/rs/src/lib.rs
soft3/strata/kuro/rs/lib.rs
soft3/tok/rs/src/lib.rs
soft3/conformance/rs/src/lib.rs
soft3/radio/iroh-base/src/lib.rs
neural/rs/macros/src/lib.rs
neural/rs/darwin-sys/src/lib.rs
soft3/radio/iroh-ffi/src/lib.rs
cyb/honeycrisp/rane/src/lib.rs
neural/rune/rs/prysm/lib.rs
soft3/lens/core/src/lib.rs
neural/rs/mir-format/src/lib.rs
neural/rs/core/src/lib.rs
soft3/strata/nebu/rs/lib.rs
neural/rune/rs/compile/lib.rs
neural/rune/rs/interp/lib.rs
soft3/strata/compute/src/lib.rs
soft3/lens/binius/src/lib.rs
soft3/strata/proof/src/lib.rs
neural/eidos/rs/src/lib.rs
neural/rune/rs/mold/lib.rs
soft3/strata/core/src/lib.rs
soft3/hemera/wgsl/src/lib.rs
neural/rune/rs/subject/lib.rs
soft3/soma/kernel/src/lib.rs
neural/rune/rs/lower/lib.rs
cyb/honeycrisp/acpu/src/lib.rs
neural/rune/rs/parse-pure/lib.rs
soft3/radio/iroh-gossip/src/lib.rs
neural/rune/rs/lex/lib.rs
soft3/hemera/rs/src/lib.rs
neural/rune/rs/ast/lib.rs
soft3/lens/ikat/src/lib.rs
soft3/tape/impl/rust/src/lib.rs
cyb/evy/crates/evy_radio/src/lib.rs
neural/inf/rs/lex/src/lib.rs
cyb/evy/forks/bevy_animation/src/lib.rs
neural/inf/rs/oracle/src/lib.rs
neural/trident/editor/zed/src/lib.rs
warriors/trisha/.vendor/twenty-first/src/lib.rs
warriors/erga/rs/pool/src/lib.rs
cyb/wysm/crates/c_api/src/lib.rs
cyb/evy/forks/bevy_anti_alias/src/lib.rs
soft3/strata/trop/rs/src/lib.rs
cyb/evy/forks/bevy_pbr/src/lib.rs
soft3/radio/quinn/quinn-udp/src/lib.rs
warriors/trisha/.vendor/triton-vm/src/lib.rs
soft3/strata/genies/rs/src/lib.rs
soft3/radio/nettools/portmapper/src/lib.rs
warriors/erga/rs/wallet/src/lib.rs
soft3/lytics/rs/core/src/lib.rs
cyb/wysm/crates/c_api/macro/lib.rs
soft3/strata/kuro/wgsl/src/lib.rs
cyb/evy/forks/bevy_tasks/src/lib.rs
neural/inf/rs/plan/src/lib.rs
cyb/wysm/crates/ir/src/lib.rs
cyb/evy/forks/bevy_gizmos/src/lib.rs
soft3/strata/genies/wgsl/src/lib.rs
warriors/erga/rs/autolykos/src/lib.rs
cyb/evy/forks/bevy_sprite/src/lib.rs
cyb/evy/crates/evy_prysm_core/src/lib.rs
warriors/trisha/.vendor/triton-constraint-circuit/src/lib.rs
cyb/evy/forks/bevy_transform/src/lib.rs
cyb/evy/crates/evy_engine_dispatch/src/lib.rs
warriors/trisha/.vendor/triton-air/src/lib.rs
cyb/evy/forks/bevy_diagnostic/src/lib.rs
cyb/wysm/crates/fuzz/src/lib.rs
neural/inf/rs/source/src/lib.rs
cyb/wysm/crates/wasmi/src/lib.rs
cyb/evy/forks/bevy_ecs/src/lib.rs
soft3/strata/jali/rs/src/lib.rs
warriors/trisha/.vendor/triton-isa/src/lib.rs
soft3/lytics/rs/event/src/lib.rs
soft3/strata/trop/wgsl/src/lib.rs
cyb/evy/crates/evy_dialect/src/lib.rs
warriors/trisha/.vendor/triton-constraint-builder/src/lib.rs
cyb/evy/crates/evy_platform_caps/src/lib.rs
cyb/evy/forks/bevy_sprite_render/src/lib.rs
cyb/evy/forks/bevy_gizmos_render/src/lib.rs
cyb/wysm/crates/collections/src/lib.rs
neural/inf/rs/ast/src/lib.rs
cyb/evy/forks/bevy_mesh/src/lib.rs
soft3/radio/quinn/bench/src/lib.rs
cyb/wysm/crates/c_api/artifact/lib.rs
warriors/erga/rs/rtable-bench/src/lib.rs
cyb/wysm/crates/core/src/lib.rs
soft3/strata/jali/wgsl/src/lib.rs
soft3/radio/iroh/bench/src/lib.rs
warriors/erga/rs/blake-bench/src/lib.rs
neural/inf/rs/parse/src/lib.rs
cyb/evy/forks/bevy_post_process/src/lib.rs
soft3/radio/quinn/quinn/src/lib.rs
cyb/evy/crates/evy_ecs_storage/src/lib.rs
warriors/erga/rs/app/src/lib.rs
neural/inf/rs/lower/src/lib.rs
cyb/evy/forks/bevy_core_pipeline/src/lib.rs
cyb/evy/forks/bevy_render/src/lib.rs
neural/rs/tests/macro-integration/src/lib.rs
soft3/radio/iroh-ffi/iroh-js/src/lib.rs
cyb/evy/forks/bevy_image/src/lib.rs
cyb/evy/crates/evy_engine_tasks/src/lib.rs
warriors/erga/rs/mine-bench/src/lib.rs
soft3/strata/nebu/wgsl/src/lib.rs
soft3/radio/nettools/netwatch/src/lib.rs
warriors/erga/rs/miner/src/lib.rs
neural/inf/rs/value/src/lib.rs
cyb/wysm/crates/wast/src/lib.rs
soft3/radio/tests/integration/src/lib.rs
cyb/wysm/crates/wasi/src/lib.rs
cyb/evy/crates/evy_diagnostic/src/lib.rs
soft3/radio/quinn/perf/src/lib.rs
soft3/radio/quinn/quinn-proto/src/lib.rs
cyb/evy/forks/naga/src/lib.rs
neural/inf/rs/eval/src/lib.rs
cyb/evy/crates/evy_engine_core/src/lib.rs
neural/inf/rs/cozo/cozo-lib-python/src/lib.rs
neural/inf/rs/cozo/cozo-lib-swift/src/lib.rs
neural/inf/rs/cozo/cozorocks/src/lib.rs
neural/inf/rs/cozo/cozo-lib-java/src/lib.rs
neural/inf/rs/cozo/cozo-lib-c/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/src/lib.rs
neural/inf/rs/cozo/cozo-core/src/lib.rs
neural/inf/rs/cozo/cozo-lib-wasm/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/unimem/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/rane/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/unimem/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/acpu/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-ad6c77c38e86bc291/aruminium/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/aruminium/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/rane/src/lib.rs
cyb/honeycrisp/.claude/worktrees/agent-aa1259cb10112b22a/acpu/src/lib.rs

Graph