#include "Utils.h"

#include "GlobalCallInvoker.h"
#include <mutex>
#include <thread>

namespace sharedjsi
{

std::string pointerToHexString(void const *ptr)
{
  char buffer[20]; // Assuming a 64-bit pointer, 16 characters for address + 2 for "0x" + 1 for null-terminator.
  snprintf(buffer, sizeof(buffer), "%p", ptr);
  return {buffer};
}

// To be used with `Option<slice_ref_uint8_t> (check Rust Core API)
slice_ref_uint8_t borrowVecAsOptRefSlice(std::vector<uint8_t> const &v)
{
  slice_ref_uint8_t it{};
  it.ptr = v.empty() ? nullptr : v.data();
  it.len = v.size();
  return it;
}

// To be used with non-optional `slice_ref_uint8_t` (check Rust Core API)
slice_ref_uint8_t borrowVecAsRefSlice(std::vector<uint8_t> const &v)
{
  slice_ref_uint8_t it{};
  it.ptr = v.empty() ? (uint8_t *)0xbad000 : v.data();
  it.len = v.size();
  return it;
}

slice_ref_dittoffi_query_result_item_ptr
borrowVecAsRefSlice(std::vector<dittoffi_query_result_item_t *> const &vector)
{
    slice_ref_dittoffi_query_result_item_ptr slice{};
    slice.ptr = vector.empty()
                ? reinterpret_cast<dittoffi_query_result_item_t **>(0xbad000)
                : const_cast<dittoffi_query_result_item_t **>(vector.data());
    slice.len = vector.size();
    return slice;
}

std::vector<uint8_t> jsTypedArrayToCVector(Runtime &runtime, const Value &arg)
{
  if (arg.isUndefined() || arg.isNull())
  {
    return {};
  }
  TypedArrayBase typedArrayBase(runtime, arg.asObject(runtime));
  return typedArrayBase.toVector(runtime);
}

std::string jsTypedArrayToCString(Runtime &runtime, const Value &arg)
{
  std::vector<uint8_t> vec = jsTypedArrayToCVector(runtime, arg);
  return {vec.begin(), vec.end()};
}



Value int64ToBigIntOrNumber(Runtime &runtime, int64_t value) {
  constexpr int64_t jsMaxSafeInteger = (1LL << 53) - 1;
  constexpr int64_t jsMinSafeInteger = -(1LL << 53) + 1;
  
  if (jsMinSafeInteger <= value && value <= jsMaxSafeInteger) {
    return static_cast<double>(value);
  } else {
    return BigInt::fromInt64(runtime, value);
  }
}

Value uint64ToBigIntOrNumber(Runtime &runtime, uint64_t value) {
  constexpr uint64_t jsMaxSafeInteger = 1ULL << (53 - 1);
  
  if (value <= jsMaxSafeInteger) {
    return static_cast<double>(value);
  } else {
    return BigInt::fromUint64(runtime, value);
  }
}

int64_t bigIntOrNumberToInt64(Runtime &runtime, Value const& value) {
  if (value.isNumber()) {
    return static_cast<int64_t>(value.asNumber());
  } else if (value.isBigInt()) {
    return value.asBigInt(runtime).asInt64(runtime);
  } else {
    throw jsi::JSError(runtime, "Expected a number or BigInt");
  }
}

uint64_t bigIntOrNumberToUint64(Runtime &runtime, Value const& value) {
  if (value.isNumber()) {
    return static_cast<uint64_t>(value.asNumber());
  } else if (value.isBigInt()) {
    return value.asBigInt(runtime).asUint64(runtime);
  } else {
    throw jsi::JSError(runtime, "Expected a number or BigInt");
  }
}

using Task = std::function<void(Runtime &)>;

struct {
    std::mutex mutex;
    std::vector<Task> tasks;
} TASKS;

Task try_recv_task() {
    std::unique_lock<std::mutex> lock(TASKS.mutex);
    
    if (TASKS.tasks.empty()) { return nullptr; }
    
    Task task{std::move(TASKS.tasks.back())};
    TASKS.tasks.pop_back();
    return task;
}

void ditto_tick(Runtime *jsi) {
    while (1) {
        Task task = try_recv_task();
        if (task == nullptr) { return; }
        task(*jsi);
    }
}

template <typename T, typename = void>
struct has_invokeAsync_with_runtime : std::false_type {};

template <typename T>
struct has_invokeAsync_with_runtime<
T,
std::void_t<decltype(std::declval<T>().invokeAsync(std::declval<std::function<void(jsi::Runtime&)>>()))>
> : std::true_type {};

template <typename T>
void invokeTask(std::shared_ptr<T> globalCallInvoker, const std::function<void(jsi::Runtime&)>& task) {
    using HasRuntime = has_invokeAsync_with_runtime<T>;
    
    if constexpr (HasRuntime::value) {
        // RN 0.76+: callInvoker has the correct signature.
        // References:
        // 1. Phillip Pan – React Native under the hood | App.js Conf 2024
        // https://www.youtube.com/watch?v=oLmGInjKU2U&t=1141s
        // 2. Accessing JavaScript land from native modules in the New Architecture
        // https://github.com/reactwg/react-native-new-architecture/discussions/160
        globalCallInvoker->invokeAsync([task](jsi::Runtime& runtime) {
            task(runtime);
        });
    } else {
        // We default to the old implementation that is based on polling.
        std::unique_lock<std::mutex> lock{TASKS.mutex};
        TASKS.tasks.emplace_back(task);
    }
}

void jsi_enqueue_call(std::function<void(jsi::Runtime&)> task) {
    // We need to call into JS world using the callInvoker. Unfortunately, the only implementation of `callInvoker->invokeAsync()` that has a Runtime parameter is available with RN 0.76+ so we use SFINAE to branch this code based on this method's availability.
    invokeTask(globalCallInvoker, task);
}
}
