#include <DQL.h>

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

namespace sharedjsi
{

DEFINE_RETAIN_RELEASE_FOR(Function, v_retain, v_release);

extern "C" {
void v_change_handler_with_query_result(void *ctx, ChangeHandlerWithQueryResult_t result)
{
  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.
  Arc<Function> jsCallback /* = */ (borrowedJsCallback);
  jsi_enqueue_call([=](Runtime &runtime) {
    dittoffi_query_result_t *query_result = result.query_result;

    Object obj(runtime);
    obj.setProperty(runtime, "query_result", cPointerToJSPointerObject(runtime, query_result));

    jsCallback->call(runtime, obj);
  });
}
}

Function dittoffi_try_exec_statement(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
                                          {
    CDitto_t *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);
    std::string query_vec = jsTypedArrayToCString(runtime, arguments[1]);
    const char *query = query_vec.c_str();

    std::vector<uint8_t> args_vec = jsTypedArrayToCVector(runtime, arguments[2]);
    slice_ref_uint8_t args_cbor = borrowVecAsOptRefSlice(args_vec);
    dittoffi_result_dittoffi_query_result_ptr res = ::dittoffi_try_exec_statement(ditto, query, args_cbor);

    Object obj(runtime);
    obj.setProperty(runtime, "success", cPointerToJSPointerObject(runtime, res.success));
    if (res.error != nullptr) {
      obj.setProperty(runtime, "error", cPointerToJSPointerObject(runtime, res.error));
    } else {
      obj.setProperty(runtime, "error", Value::null());
    }
    return obj;
  });
}

Function dittoffi_try_experimental_register_change_observer_str_detached(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 *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);
    std::string query_str = jsTypedArrayToCString(runtime, arguments[1]);
    const char *query = query_str.c_str();

    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 jsCallback = arguments[3].getObject(runtime).getFunction(runtime);
    Arc<Function> wrappedStateFfiWrapper(std::move(jsCallback));

    LiveQueryAvailability lq_availability = LIVE_QUERY_AVAILABILITY_ALWAYS;
    //    std::string lq_availability_str = arguments[3].getString(runtime).utf8(runtime);
    //    if (lq_availability_str == "Always") {
    //      lq_availability = LIVE_QUERY_AVAILABILITY_ALWAYS;
    //    } if (lq_availability_str == "WhenSignalled") {
    //      lq_availability = LIVE_QUERY_AVAILABILITY_WHEN_SIGNALLED;
    //    } else {
    //      throw std::invalid_argument("Invalid live query string");
    //    }

    dittoffi_result_int64_t res = ::dittoffi_try_experimental_register_change_observer_str(
                                                                                           ditto,
                                                                                           query,
                                                                                           query_args_cbor,
                                                                                           lq_availability,
                                                                                           Arc<Function>::as_raw(wrappedStateFfiWrapper),
                                                                                           v_retain,
                                                                                           v_release,
                                                                                           v_change_handler_with_query_result);

    Object obj(runtime);
    obj.setProperty(runtime, "success", int64ToBigIntOrNumber(runtime, res.success));
    if (res.error != nullptr) {
      obj.setProperty(runtime, "error", cPointerToJSPointerObject(runtime, res.error));
    } else {
      obj.setProperty(runtime, "error", Value::null());
    }
    return obj;
  });
}


Function dittoffi_query_result_item_count(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
                                          {
    size_t res = ::dittoffi_query_result_item_count(jsPointerObjectToCPointer<dittoffi_query_result>(runtime, arguments[0]));
    return static_cast<double>(res);
  });
}


Function dittoffi_query_result_item_at(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 *dql_response = jsPointerObjectToCPointer<dittoffi_query_result>(runtime, arguments[0]);
    size_t idx = arguments[1].asNumber();

    dittoffi_query_result_item_t *res = ::dittoffi_query_result_item_at(dql_response, idx);
    return cPointerToJSPointerObject(runtime, res);
  });
}

Function dittoffi_query_result_mutated_document_id_count(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
                                          {
    size_t res = ::dittoffi_query_result_mutated_document_id_count(jsPointerObjectToCPointer<dittoffi_query_result>(runtime, arguments[0]));
    return static_cast<double>(res);
  });
}

Function dittoffi_query_result_mutated_document_id_at(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 *dql_response = jsPointerObjectToCPointer<dittoffi_query_result>(runtime, arguments[0]);
    size_t idx = arguments[1].asNumber();

    slice_boxed_uint8_t res = ::dittoffi_query_result_mutated_document_id_at(dql_response, idx);
    return cSlicedBoxToJSSlicedBox(runtime, res);
  });
}

Function dittoffi_query_result_item_cbor(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
                                          {
    slice_boxed_uint8_t res = ::dittoffi_query_result_item_cbor(jsPointerObjectToCPointer<dittoffi_query_result_item_t>(runtime, arguments[0]));
    return cSlicedBoxToJSSlicedBox(runtime, res);
  });
}

Function dittoffi_query_result_item_json(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
                                          {

    char *res = ::dittoffi_query_result_item_json(jsPointerObjectToCPointer<dittoffi_query_result_item_t>(runtime, arguments[0]));
    return cPointerToJSPointerObject<char>(runtime, res);
  });
}

Function dittoffi_query_result_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_query_result_free(jsPointerObjectToCPointer<dittoffi_query_result>(runtime, arguments[0]));
    return {};
  });
}

Function dittoffi_query_result_item_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_query_result_item_free(jsPointerObjectToCPointer<dittoffi_query_result_item>(runtime, arguments[0]));
    return {};
  });
}

Function dittoffi_query_result_item_new(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
                                          {
                                            std::vector<uint8_t> jsonVec = jsTypedArrayToCVector(runtime, arguments[0]);
                                            slice_ref_uint8_t jsonSlice = borrowVecAsRefSlice(jsonVec);
                                            auto res = ::dittoffi_query_result_item_new(jsonSlice);

                                            Object out(runtime);
                                            out.setProperty(runtime, "success", cPointerToJSPointerObject(runtime, res.success));
                                            out.setProperty(runtime, "error", cPointerToJSPointerObject(runtime, res.error));
                                            return out;
                                          });
}

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

Function dittoffi_query_result_commit_id(Runtime &runtime)
{
  return Function::createFromHostFunction(runtime,
                                          PropNameID::forAscii(runtime, STRINGIFIED_FUNC_NAME()),
                                          1,
                                          [](Runtime &runtime,
                                             const Value &thisValue,
                                             const Value *arguments,
                                             size_t count) -> Value
                                          {
    uint64_t res = ::dittoffi_query_result_commit_id(jsPointerObjectToCPointer<dittoffi_query_result>(runtime, arguments[0]));
    return uint64ToBigIntOrNumber(runtime, res);
  });
}
}
