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

#include <Presence.h>

namespace sharedjsi
{

DEFINE_RETAIN_RELEASE_FOR(Function, v_retain, v_release);
DEFINE_RETAIN_RELEASE_ERASED_T_FOR(Function, retain_vptr, release_vptr);

extern "C"
{
void v_presence_callback(void *ctx, char const *str)
{
  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), cpp_string = std::string(str)](Runtime &runtime) {
    jsCallback->call(runtime, cPointerToJSPointerObject<char>(runtime, cpp_string.c_str()));
  });
}


void v_connection_handler(Erased_t const* ctx, dittoffi_connection_request *ffi_request)
{
  auto ptr = (void *)(ctx);
  Arc<Function>& borrowedJsCallback = Arc<Function>::borrow_from_raw(ptr);
  // 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) {
    auto obj = cPointerToJSPointerObject<dittoffi_connection_request_t>(runtime, ffi_request);
    jsCallback->call(runtime, obj);
  });
}
}

Function ditto_presence_v3(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 = ::ditto_presence_v3(jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]));
    return cPointerToJSPointerObject<char>(runtime, res);
  });
}

Function ditto_register_presence_v3_callback(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]);
    Function presence_callback = arguments[1].getObject(runtime).getFunction(runtime);
    Arc<Function> wrappedStateFfiWrapper /* = */ (std::move(presence_callback));

    ::ditto_register_presence_callback_v3(ditto,
                                          Arc<Function>::as_raw(wrappedStateFfiWrapper),
                                          v_retain,
                                          v_release,
                                          v_presence_callback);
    return {};
  });
}

Function ditto_clear_presence_v3_callback(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
                                          {
    ::ditto_clear_presence_v3_callback(jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]));
    return {};
  });
}

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

Function dittoffi_presence_try_set_peer_metadata_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
                                          {
    auto *ditto = jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]);

    TypedArrayBase typedArrayBase(runtime, arguments[1].asObject(runtime));
    std::vector<uint8_t> peer_info_vec = typedArrayBase.toVector(runtime);
    slice_ref_uint8_t peer_info = { .ptr = peer_info_vec.data(), .len = peer_info_vec.size() };

    dittoffi_result_void_t res = ::dittoffi_presence_try_set_peer_metadata_json(ditto, peer_info);

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


Function dittoffi_presence_set_connection_request_handler(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]);

    Function jsCallback = arguments[1].getObject(runtime).getFunction(runtime);
    Arc<Function> wrappedStateFfiWrapper(std::move(jsCallback));

    ::dittoffi_presence_set_connection_request_handler(ditto, {
      .ptr = (Erased_t *)Arc<Function>::into_raw(std::move(wrappedStateFfiWrapper)),
      .vtable = {
        .release_vptr = release_vptr,
        .retain_vptr = retain_vptr,
        .on_connecting = v_connection_handler,
      },
    });
    return {};
  });
}

Function ditto_clear_presence_callback(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
                                          {
    ::ditto_clear_presence_callback(jsPointerObjectToCPointer<CDitto_t>(runtime, arguments[0]));
    return {};
  });
}

}
