/**
 *
 * Agora Real Time Engagement
 * Created by Wei Hu in 2022-09.
 * Copyright (c) 2022 Agora IO. All rights reserved.
 *
 */
#include "rte_runtime/binding/nodejs/src/value/js_ref.h"

#include <assert.h>

#include "js_native_api.h"
#include "js_native_api_types.h"
#include "lib/alloc.h"
#include "node_api.h"
#include "node_api_types.h"
#include "rte_runtime/binding/nodejs/src/common/common.h"
#include "rte_runtime/sanitizer/thread_check.h"
#include "zf_log.h"

UNUSED static bool rte_nodejs_js_ref_check_integrity(rte_nodejs_js_ref_t* self,
                                                     bool check_thread) {
  assert(self);

  if (rte_signature_get(&self->signature) != RTE_NODEJS_JS_REF_SIGNATURE) {
    return false;
  }

  if (check_thread &&
      !rte_sanitizer_thread_check_do_check(&self->thread_check)) {
    return false;
  }

  return true;
}

static void js_ref_unlink_handler(rte_nodejs_js_ref_t* js_ref_bridge) {
  assert(js_ref_bridge &&
         // Important! this function would be called in the JS main thread.
         rte_nodejs_js_ref_check_integrity(js_ref_bridge, true));
  assert(js_ref_bridge->js_ref);

  ZF_LOGV("js_ref unlink handler.");

  napi_env env = js_ref_bridge->env;
  assert(env);

  // Decrement the reference count of the JS instance, so that it can be garbage
  // collected.
  napi_status status = napi_reference_unref(env, js_ref_bridge->js_ref, NULL);
  GOTO_LABEL_IF_NAPI_FAIL(error, status == napi_ok,
                          "Failed to unref js_ref: %d", status);

error:
  rte_sanitizer_thread_check_deinit(&js_ref_bridge->thread_check);
  rte_free(js_ref_bridge);
}

rte_value_t* rte_nodejs_js_ref_create(napi_env env, napi_value js_value) {
  napi_ref js_ref = NULL;

  ZF_LOGV("Create js_ref.");

  // Grab a reference to the napi_value, and set the _initial_ reference count
  // to 1, so that developers do not need to maintain a JS reference for it in
  // the JS world to prevent it from garbage collected. It's kind of more
  // developer friendly, and is in line with JS usage.
  napi_status status =
      napi_create_reference(env, js_value, 1 /* important */, &js_ref);
  ASSERT_IF_NAPI_FAIL(status == napi_ok,
                      "Failed to create C reference to JS value content: %d",
                      status);

  rte_nodejs_js_ref_t* js_ref_bridge = rte_malloc(sizeof(rte_nodejs_js_ref_t));
  assert(js_ref_bridge);

  rte_signature_set(&js_ref_bridge->signature, RTE_NODEJS_JS_REF_SIGNATURE);
  rte_sanitizer_thread_check_init_with_current_thread(
      &js_ref_bridge->thread_check);

  // Important! All operations related to this js_ref_bridge would all occurred
  // in the JS main thread, so we keep the napi_env for later use.
  js_ref_bridge->env = env;

  js_ref_bridge->js_ref = js_ref;

  // Use a shared pointer to manage the lifecycle of the JS instance.
  //
  // Important! 'js_ref_unlink_handler' would be called in the JS main thread
  // when the corresponding JS value is finalized.
  rte_sharedptr_t* js_ref_bridge_shared =
      rte_sharedptr_create(js_ref_bridge, js_ref_unlink_handler);
  assert(js_ref_bridge_shared);

  rte_value_t* c_value = rte_value_create_shared_ptr(js_ref_bridge_shared);
  assert(c_value);

  rte_sharedptr_dec_rc(js_ref_bridge_shared);

  ZF_LOGV("Create js_ref done.");

  return c_value;
}
