#include "Utils.h"
#include "Arc.hpp"

#include <Misc.h>

namespace sharedjsi
{

/* Post-condition: refcount -= 1; dtor untouched. */
static void v_release(void *ptr) {
  Arc<Function>::from_raw(ptr);
}

extern "C"
{
void v_transaction_result(void *ctx, dittoffi_result_dittoffi_transaction_ptr_t res)
{
  Arc<Function> const& borrowedJsCallback = Arc<Function>::borrow_from_raw(ctx);
  // copy ctor / retain so as to keep the ArcPointee<Function> alive until the enqueued_call completes.
  jsi_enqueue_call([=, jsCallback=Arc<Function>(borrowedJsCallback)](Runtime &runtime) {
    Object obj(runtime);
    obj.setProperty(runtime, "success", cPointerToJSPointerObject(runtime, res.success));
    obj.setProperty(runtime, "error", cPointerToJSPointerObject(runtime, res.error));
    jsCallback->call(runtime, obj);
  });
}

void v_transaction_completion_action_result(void *ctx, dittoffi_result_dittoffi_transaction_completion_action_t res)
{
  Arc<Function> const& borrowedJsCallback = Arc<Function>::borrow_from_raw(ctx);
  // copy ctor / retain so as to keep the ArcPointee<Function> alive until the enqueued_call completes.
  jsi_enqueue_call([=, jsCallback=Arc<Function>(borrowedJsCallback)](Runtime &runtime) {
    std::string action;
    switch (res.success) {
      case DITTOFFI_TRANSACTION_COMPLETION_ACTION_COMMIT:
        action = "Commit";
        break ;
      case DITTOFFI_TRANSACTION_COMPLETION_ACTION_ROLLBACK:
        action = "Rollback";
        break;
    }
    
    Object obj(runtime);
    obj.setProperty(runtime, "success", action);
    obj.setProperty(runtime, "error", cPointerToJSPointerObject(runtime, res.error));
    jsCallback->call(runtime, obj);
  });
}

void v_transaction_query_result(void *ctx, dittoffi_result_dittoffi_query_result_ptr_t res)
{
  Arc<Function> const& borrowedJsCallback = Arc<Function>::borrow_from_raw(ctx);
  // copy ctor / retain so as to keep the ArcPointee<Function> alive until the enqueued_call completes.
  jsi_enqueue_call([=, jsCallback=Arc<Function>(borrowedJsCallback)](Runtime &runtime) {
    Object obj(runtime);
    obj.setProperty(runtime, "success", cPointerToJSPointerObject(runtime, res.success));
    obj.setProperty(runtime, "error", cPointerToJSPointerObject(runtime, res.error));
    jsCallback->call(runtime, obj);
  });
}

}

Function dittoffi_store_transactions(Runtime &runtime) {
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime, STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value {
    dittoffi_cbor_data_t res = ::dittoffi_store_transactions(jsPointerObjectToCPointer<dittoffi_store_t>(runtime, arguments[0]));
    return cSlicedBoxToJSSlicedBox(runtime, res);
  });
}

Function dittoffi_store_begin_transaction_async_throws(Runtime &runtime) {
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime, STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value {
    auto *store = jsPointerObjectToCPointer<dittoffi_store_t>(runtime, arguments[0]);
    auto optionsObj = arguments[1].asObject(runtime);
    Function callback = arguments[2].getObject(runtime).getFunction(runtime);
    Arc<Function> wrappedStateFfiWrapper(std::move(callback));
    
    dittoffi_store_begin_transaction_options_t options = {0};
    
    if (optionsObj.getProperty(runtime, "is_read_only").isBool()) {
      options.is_read_only = optionsObj.getProperty(runtime, "is_read_only").asBool();
    }
    
    std::string hint_str;
    if (!optionsObj.getProperty(runtime, "hint").isNull()) {
      hint_str = jsTypedArrayToCString(runtime, optionsObj.getProperty(runtime, "hint"));
      options.hint = hint_str.c_str();
    }
    
    ::dittoffi_store_begin_transaction_async_throws(store, options, {
      .env_ptr = Arc<Function>::into_raw(std::move(wrappedStateFfiWrapper)),
      .call = v_transaction_result,
      .free = v_release,
    });
    return Value();
  });
}

Function dittoffi_transaction_complete_async_throws(Runtime &runtime) {
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime, STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value {
    auto *transaction = jsPointerObjectToCPointer<dittoffi_transaction_t>(runtime, arguments[0]);
    std::string action_str = arguments[1].asString(runtime).utf8(runtime);
    Function callback = arguments[2].getObject(runtime).getFunction(runtime);
    Arc<Function> wrappedStateFfiWrapper(std::move(callback));
    
    dittoffi_transaction_completion_action_t action;
    if (action_str == "Commit") {
      action = DITTOFFI_TRANSACTION_COMPLETION_ACTION_COMMIT;
    } else if (action_str == "Rollback") {
      action = DITTOFFI_TRANSACTION_COMPLETION_ACTION_ROLLBACK;
    } else {
      throw "Invalid transaction completing action provided.";
    }
    
    ::dittoffi_transaction_complete_async_throws(transaction, action, {
      .env_ptr = Arc<Function>::into_raw(std::move(wrappedStateFfiWrapper)),
      .call = v_transaction_completion_action_result,
      .free = v_release,
    });
    return Value();
  });
}

Function dittoffi_transaction_execute_async_throws(Runtime &runtime) {
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime, STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value {
    auto *transaction = jsPointerObjectToCPointer<dittoffi_transaction_t>(runtime, arguments[0]);
    std::string query_str = jsTypedArrayToCString(runtime, arguments[1]);
    std::vector<uint8_t> query_args_cbor_vec = jsTypedArrayToCVector(runtime, arguments[2]);
    slice_ref_uint8_t query_args_cbor = borrowVecAsOptRefSlice(query_args_cbor_vec);
    Function callback = arguments[3].getObject(runtime).getFunction(runtime);
    Arc<Function> wrappedStateFfiWrapper(std::move(callback));
    
    ::dittoffi_transaction_execute_async_throws(transaction, query_str.c_str(), query_args_cbor, {
      .env_ptr = Arc<Function>::into_raw(std::move(wrappedStateFfiWrapper)),
      .call = v_transaction_query_result,
      .free = v_release,
    });
    return Value();
  });
}

Function dittoffi_transaction_info(Runtime &runtime) {
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime, STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value {
  
    
    dittoffi_cbor_data_t res = ::dittoffi_transaction_info(jsPointerObjectToCPointer<dittoffi_transaction_t>(runtime, arguments[0]));
    return cSlicedBoxToJSSlicedBox(runtime, res);
  });
}

Function dittoffi_transaction_free(Runtime &runtime) {
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime, STRINGIFIED_FUNC_NAME()),
                                          0,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value {
  
    
    ::dittoffi_transaction_free(jsPointerObjectToCPointer<dittoffi_transaction_t>(runtime, arguments[0]));
    return Value();
  });
}
}
