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

#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>

#include "jansson.h"
#include "js_native_api.h"
#include "lib/alloc.h"
#include "lib/error.h"
#include "lib/list.h"
#include "lib/list_node.h"
#include "lib/mutex.h"
#include "lib/rwlock.h"
#include "lib/shared_ptr.h"
#include "lib/signature.h"
#include "lib/string.h"
#include "macro/mark.h"
#include "rte_runtime/binding/nodejs/src/common/common.h"
#include "rte_runtime/binding/nodejs/src/common/semaphore.h"
#include "rte_runtime/binding/nodejs/src/common/tsfn.h"
#include "rte_runtime/binding/nodejs/src/extension/extension.h"
#include "rte_runtime/binding/nodejs/src/metadata/metadata.h"
#include "rte_runtime/binding/nodejs/src/msg/cmd/cmd.h"
#include "rte_runtime/binding/nodejs/src/msg/image_frame/image_frame.h"
#include "rte_runtime/binding/nodejs/src/msg/pcm_frame/pcm_frame.h"
#include "rte_runtime/binding/nodejs/src/value/value.h"
#include "rte_runtime/common/loc.h"
#include "rte_runtime/extension/extension.h"
#include "rte_runtime/extension_thread/extension_thread.h"
#include "rte_runtime/msg/cmd/cmd.h"
#include "rte_runtime/msg/data/data.h"
#include "rte_runtime/rte/internal/on_xxx.h"
#include "rte_runtime/rte/rte.h"
#include "rte_runtime/sanitizer/thread_check.h"
#include "third_party/node/include/node/js_native_api.h"

#define ZF_LOG_TAG "Node"
#include "zf_log.h"

typedef struct send_xxx_callback_call_info_t {
  rte_nodejs_extension_t* extension_bridge;
  rte_sharedptr_t* cmd_status;
  rte_nodejs_threadsafe_function_t* rte_tsfn;
  rte_nodejs_sem_t sem;

  // Determine if we can 'release' the JavaScript callback. If we receive all
  // the responses, we can release the JS callback, otherwise, the JS callback
  // must be keep alive (prevent it from being garbage collected).
  bool receive_all_responses;
} send_xxx_callback_call_info_t;

static napi_ref js_rte_constructor_ref = NULL;  // NOLINT

bool rte_nodejs_rte_check_integrity(rte_nodejs_rte_t* self, bool check_thread) {
  assert(self);

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

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

  return true;
}

static napi_value rte_nodejs_rte_register_class(napi_env env,
                                                napi_callback_info info) {
  assert(env);
  assert(info);

  const size_t argc = 1;
  napi_value argv[argc];  // RTE
  if (!rte_nodejs_get_args(env, info, argv, argc)) {
    return UNDEFINED(env);
  }

  napi_status status =
      napi_create_reference(env, argv[0], 1, &js_rte_constructor_ref);
  ASSERT_IF_NAPI_FAIL(
      status == napi_ok,
      "Failed to create C reference to JS 'rte' constructor: %d", status);

  return UNDEFINED(env);
}

static void rte_nodejs_rte_destroy(rte_nodejs_rte_t* self) {
  assert(self &&
         // RTE_NOLINTNEXTLINE(thread-check)
         // thread-check: if this function is called, it means both the JS rte &
         // C rte end, so no more access to this rte bridge will happen, so it's
         // thread safe.
         rte_nodejs_rte_check_integrity(self, false));

  rte_rwlock_destroy(self->is_close_lock);
  rte_sanitizer_thread_check_deinit(&self->thread_check);
  rte_free(self);
}

static void rte_nodejs_rte_finalize(napi_env env, void* data,
                                    UNUSED void* hint) {
  rte_nodejs_rte_t* rte_bridge = data;
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  ZF_LOGV("RTE JS rte object is finalized.");

  // According to NAPI doc:
  // ====
  // Caution: The optional returned reference (if obtained) should be deleted
  // via napi_delete_reference __ONLY__ in response to the finalize callback
  // invocation. If it is deleted before then, then the finalize callback may
  // never be invoked. Therefore, when obtaining a reference a finalize callback
  // is also required in order to enable correct disposal of the reference.
  napi_delete_reference(env, rte_bridge->js_ref);

  // The JS rte is going to be destroyed, decrease the reference count of the
  // bridge to indicate that one of the ownership of the rte bridge will not be
  // held by JS rte.
  rte_sharedptr_dec_rc(rte_bridge->shared_ptr);
}

static void rte_nodejs_rte_close(void* rte_bridge_) {
  rte_nodejs_rte_t* rte_bridge = (rte_nodejs_rte_t*)rte_bridge_;
  assert(rte_bridge &&
         // RTE_NOLINTNEXTLINE(thread-check)
         // thread-check: this function is meant to be called in all threads, so
         // we use mutex to protect it.
         rte_nodejs_rte_check_integrity(rte_bridge, false));

  rte_rwlock_lock(rte_bridge->is_close_lock, 0);
  rte_bridge->is_close = true;
  rte_rwlock_unlock(rte_bridge->is_close_lock, 0);
}

static void rte_nodejs_rte_destroy_c_part(void* rte_bridge_) {
  rte_nodejs_rte_t* rte_bridge = (rte_nodejs_rte_t*)rte_bridge_;
  assert(rte_bridge &&
         // RTE_NOLINTNEXTLINE(thread-check)
         // this function would be called from the native thread, and all
         // operations in this function is thread-safe.
         rte_nodejs_rte_check_integrity(rte_bridge, false));

  // The C rte is going to be destroyed, decrease the reference count
  // of the bridge to indicate that one of the ownership of the rte
  // bridge will not be held by C rte.
  rte_sharedptr_dec_rc(rte_bridge->shared_ptr);
}

napi_value rte_nodejs_rte_wrap(napi_env env, rte_t* rte,
                               rte_nodejs_rte_t** out_rte_bridge) {
  assert(env);
  assert(rte && rte_check_integrity(rte));

  rte_nodejs_rte_t* rte_bridge =
      (rte_nodejs_rte_t*)rte_malloc(sizeof(rte_nodejs_rte_t));
  assert(rte_bridge);

  rte_signature_set(&rte_bridge->signature, RTE_NODEJS_RTE_SIGNATURE);

  rte_sanitizer_thread_check_init_with_current_thread(
      &rte_bridge->thread_check);

  rte_bridge->c_rte = rte;
  rte_bridge->js_ref = NULL;

  rte_bridge->is_close = false;
  rte_bridge->is_close_lock = rte_rwlock_create(RTE_RW_DEFAULT_FAIRNESS);

  // The initial reference count (1) indicates that the C rte references to the
  // bridge.
  rte_bridge->shared_ptr =
      rte_sharedptr_create(rte_bridge, rte_nodejs_rte_destroy);

  rte_set_me_in_target_lang(rte, rte_bridge);
  rte_set_close_handler_in_target_lang(rte, rte_nodejs_rte_close);
  rte_set_destroy_handler_in_target_lang(rte, rte_nodejs_rte_destroy_c_part);

  rte_list_init(&rte_bridge->tsfns);

  napi_value instance = rte_nodejs_create_new_js_object_and_wrap(
      env, js_rte_constructor_ref, rte_bridge, rte_nodejs_rte_finalize,
      &rte_bridge->js_ref);
  if (!instance) {
    goto error;
  }

  // Increment the sharedptr reference count to indicate that the JS rte
  // references to the bridge.
  rte_sharedptr_inc_rc(rte_bridge->shared_ptr);

  goto done;

error:
  if (rte_bridge) {
    rte_free(rte_bridge);
    rte_bridge = NULL;
  }

done:
  if (out_rte_bridge) {
    *out_rte_bridge = rte_bridge;
  }

  return instance;
}

static bool proxy_send_xxx_callback(rte_extension_t* extension,
                                    UNUSED rte_t* rte,
                                    rte_sharedptr_t* cmd_status,
                                    void* rte_tsfn_) {
  assert(extension && rte_extension_check_integrity(extension));
  assert(rte && rte_check_integrity(rte));
  assert(cmd_status && rte_cmd_check_integrity(cmd_status));
  assert(rte_tsfn_);

  rte_nodejs_threadsafe_function_t* rte_tsfn = rte_tsfn_;
  assert(rte_tsfn &&
         rte_nodejs_threadsafe_function_check_integrity(rte_tsfn, false));

  rte_nodejs_extension_t* extension_bridge =
      rte_extension_get_me_in_target_lang(extension);
  assert(extension_bridge &&
         // RTE_NOLINTNEXTLINE(thread-check)
         // thread-check: in order to maintain thread safety, we use semaphore
         // below to prevent JS main thread and the RTE native thread access the
         // bridge at the same time.
         rte_nodejs_extension_check_integrity(extension_bridge, false));

  send_xxx_callback_call_info_t call_info = {
      extension_bridge,
      cmd_status,
      rte_tsfn,
      {0},
      rte_receive_all_responses(rte, cmd_status),
  };
  rte_nodejs_sem_init(&call_info.sem);

  bool rc = rte_nodejs_threadsafe_function_invoke(rte_tsfn, &call_info);
  if (rc) {
    rte_nodejs_sem_wait(&call_info.sem);
  } else {
    assert(0 && "Should not happen.");

    rte_string_t cmd_name;
    rte_string_init_with_value(&cmd_name, "%s",
                               rte_msg_get_cmd_name(cmd_status));
    rte_on_cmd_response_done(rte, rte_cmd_get_cmd_id(cmd_status), &cmd_name, rc,
                             NULL);
    rte_string_deinit(&cmd_name);
  }
  rte_nodejs_sem_cleanup(&call_info.sem);
  return rc;
}

static void invoke_send_xxx_callback(napi_env env, napi_value fn,
                                     UNUSED void* context, void* data) {
  // NOTE: The context of this function is in the JS main thread, so the thread
  // safety needs to be considered.

  send_xxx_callback_call_info_t* call_info = data;
  assert(call_info);

  rte_nodejs_extension_t* extension_bridge = call_info->extension_bridge;
  assert(extension_bridge &&
         rte_nodejs_extension_check_integrity(extension_bridge, true));

  assert(rte_extension_thread_not_call_by_me(
      extension_bridge->c_extension->extension_thread));

  napi_value js_rte = NULL;
  napi_status status =
      napi_get_reference_value(env, extension_bridge->js_rte_ref, &js_rte);
  GOTO_LABEL_IF_NAPI_FAIL(done, status == napi_ok && js_rte != NULL,
                          "Failed to get JS rte: %d", status);

  napi_value js_cmd = rte_nodejs_cmd_wrap(env, call_info->cmd_status);
  assert(js_cmd);

  napi_value args[] = {js_rte, js_cmd};
  napi_value result = NULL;
  status = napi_call_function(env, js_rte, fn, 2, args, &result);
  CHECK_IF_NAPI_FAIL(status == napi_ok,
                     "Failed to call JS callback of send_xxx(): %d", status);

done:
  if (call_info->receive_all_responses) {
    // Only if we receive all the responses, we can release this JS callback.
    rte_nodejs_threadsafe_function_release(env, call_info->rte_tsfn);

    rte_nodejs_rte_t* rte_bridge =
        extension_bridge->c_extension->rte->me_in_target_lang;
    assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

    // The JS callback is released, so we remove the corresponding record in the
    // following list.
    rte_list_remove_ptr(&rte_bridge->tsfns, call_info->rte_tsfn);
  }

  rte_nodejs_sem_signal(&call_info->sem);
}

static napi_value rte_nodejs_rte_send_json(napi_env env,
                                           napi_callback_info info) {
  assert(env);

  rte_string_t json_str;
  rte_string_init(&json_str);

  const size_t argc = 3;
  napi_value args[argc];  // this, json_str, function
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    goto done;
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  if (!rte_nodejs_get_str(env, args[1], &json_str)) {
    goto done;
  }

  json_error_t error;
  json_t* json = json_loads(rte_string_c_str(&json_str), 0, &error);
  if (!json) {
    rte_string_t err_str;
    rte_string_init_with_value(&err_str, "Failed to parse json at %d:%d",
                               error.line, error.column);

    status = napi_throw_error(env, "EINVAL", rte_string_c_str(&err_str));
    ASSERT_IF_NAPI_FAIL(status == napi_ok, "Failed to throw JS exception: %d",
                        status);

    rte_string_deinit(&err_str);

    goto done;
  }

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  if (is_undefined(env, args[2])) {
    rte_send_json(rte_bridge->c_rte, json, NULL, NULL, NULL, false, NULL);
  } else {
    rte_nodejs_threadsafe_function_t* rte_tsfn =
        rte_nodejs_threadsafe_function_create(
            env, "rte::sendJson response callback", args[2],
            invoke_send_xxx_callback);
    assert(rte_tsfn);

    rte_error_t err_ctx;
    rte_error_init(&err_ctx);

    if (!rte_send_json(rte_bridge->c_rte, json, proxy_send_xxx_callback,
                       rte_tsfn, NULL, false, &err_ctx)) {
      rte_string_t code_str;
      rte_string_init_with_value(&code_str, "%d",
                                 rte_error_last_errno(&err_ctx));

      status = napi_throw_error(env, rte_string_c_str(&code_str),
                                rte_error_last_errmsg(&err_ctx));
      ASSERT_IF_NAPI_FAIL(status == napi_ok, "Failed to throw JS exception: %d",
                          status);

      rte_string_deinit(&code_str);

      // The JS callback will not be used in the future, so we release it now.
      rte_nodejs_threadsafe_function_release(env, rte_tsfn);
    } else {
      // The JS callback might be used in the future, so we record it in 'tsfns'
      // list to prevent possible memory leaks.
      rte_listnode_t* tsfn_node = rte_ptr_listnode_create(rte_tsfn, NULL);
      assert(tsfn_node);

      rte_list_push_back(&rte_bridge->tsfns, tsfn_node);
    }

    rte_error_deinit(&err_ctx);
  }

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

  json_decref(json);

done:
  rte_string_deinit(&json_str);
  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_send_cmd(napi_env env,
                                          napi_callback_info info) {
  napi_value args[3];  // this, cmd, function
  if (!rte_nodejs_get_args(env, info, args, 3)) {
    goto done;
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  rte_nodejs_cmd_t* cmd_bridge = NULL;
  status = napi_unwrap(env, args[1], (void**)&cmd_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok,
                                "Failed to get command bridge: %d", status);

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  if (is_undefined(env, args[2])) {
    rte_send_cmd(rte_bridge->c_rte, cmd_bridge->msg.c_msg, NULL, NULL, NULL,
                 false, NULL);
  } else {
    rte_nodejs_threadsafe_function_t* rte_tsfn =
        rte_nodejs_threadsafe_function_create(
            env, "rte::sendCmd response callback", args[2],
            invoke_send_xxx_callback);
    assert(rte_tsfn);

    rte_error_t err_ctx;
    rte_error_init(&err_ctx);

    if (!rte_send_cmd(rte_bridge->c_rte, cmd_bridge->msg.c_msg,
                      proxy_send_xxx_callback, rte_tsfn, NULL, false,
                      &err_ctx)) {
      rte_string_t code_str;
      rte_string_init_with_value(&code_str, "%d",
                                 rte_error_last_errno(&err_ctx));

      status = napi_throw_error(env, rte_string_c_str(&code_str),
                                rte_error_last_errmsg(&err_ctx));
      ASSERT_IF_NAPI_FAIL(status == napi_ok, "Failed to throw JS exception: %d",
                          status);

      rte_string_deinit(&code_str);

      // The JS callback will not be used in the future, so we release it now.
      rte_nodejs_threadsafe_function_release(env, rte_tsfn);
    } else {
      // The JS callback might be used in the future, so we record it in 'tsfns'
      // list to prevent possible memory leaks.
      rte_listnode_t* tsfn_node = rte_ptr_listnode_create(rte_tsfn, NULL);
      assert(tsfn_node);

      rte_list_push_back(&rte_bridge->tsfns, tsfn_node);
    }

    rte_error_deinit(&err_ctx);
  }

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_send_data(napi_env env,
                                           napi_callback_info info) {
  napi_value args[2];  // this, array_buffer
  if (!rte_nodejs_get_args(env, info, args, 2)) {
    goto done;
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  size_t size = 0;
  // Unlike string, getting array buffer pointer doesn't copy.
  char* raw_buf = rte_nodejs_get_buffer(env, args[1], &size);
  if (!raw_buf) {
    goto done;
  }

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  rte_sharedptr_t* data_ptr = rte_data_create();
  char* buf = NULL;
  if (size) {
    buf = rte_malloc(size);
    memcpy(buf, raw_buf, size);
  }
  rte_data_set_buf(data_ptr, buf);
  rte_data_set_buf_size(data_ptr, size);
  rte_send_data(rte_bridge->c_rte, data_ptr, false, NULL);
  rte_sharedptr_dec_rc(data_ptr);

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_send_image_frame(napi_env env,
                                                  napi_callback_info info) {
  napi_value args[2];  // this, image_frame
  if (!rte_nodejs_get_args(env, info, args, 2)) {
    goto done;
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  rte_nodejs_image_frame_t* image_frame_bridge = NULL;
  status = napi_unwrap(env, args[0], (void**)&image_frame_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && image_frame_bridge != NULL,
                                "Failed to get image frame bridge: %d", status);

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  rte_send_image_frame(rte_bridge->c_rte, image_frame_bridge->msg.c_msg, false,
                       NULL);

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_send_pcm_frame(napi_env env,
                                                napi_callback_info info) {
  napi_value args[2];  // this, pcm_frame
  if (!rte_nodejs_get_args(env, info, args, 2)) {
    goto done;
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  rte_nodejs_pcm_frame_t* pcm_frame_bridge = NULL;
  status = napi_unwrap(env, args[0], (void**)&pcm_frame_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && pcm_frame_bridge != NULL,
                                "Failed to get pcm_frame bridge: %d", status);

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  rte_send_pcm_frame(rte_bridge->c_rte, pcm_frame_bridge->msg.c_msg, false,
                     NULL);

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_return_text(napi_env env,
                                             napi_callback_info info) {
  assert(env);

  rte_string_t status_code;
  rte_string_init(&status_code);

  rte_string_t text;
  rte_string_init(&text);

  const size_t argc = 5;
  napi_value args[argc];  // this, status_code, json_str, cmd, err_context
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    goto done;
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  if (!rte_nodejs_get_str(env, args[1], &status_code)) {
    goto done;
  }

  if (!rte_nodejs_get_str(env, args[2], &text)) {
    goto done;
  }

  rte_nodejs_cmd_t* cmd_bridge = NULL;
  status = napi_unwrap(env, args[3], (void**)&cmd_bridge);
  ASSERT_IF_NAPI_FAIL(status == napi_ok && cmd_bridge != NULL,
                      "Failed to get cmd bridge: %d", status);

  rte_error_t err_ctx;
  rte_error_init(&err_ctx);

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  if (!rte_return_text(rte_bridge->c_rte, rte_string_c_str(&status_code),
                       rte_string_c_str(&text), cmd_bridge->msg.c_msg, false,
                       NULL)) {
    rte_string_t code_str;
    rte_string_init_with_value(&code_str, "%d", rte_error_last_errno(&err_ctx));

    status = napi_throw_error(env, rte_string_c_str(&code_str),
                              rte_error_last_errmsg(&err_ctx));
    ASSERT_IF_NAPI_FAIL(status == napi_ok, "Failed to throw JS exception: %d",
                        status);

    rte_string_deinit(&code_str);
  }

  rte_error_deinit(&err_ctx);

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  rte_string_deinit(&status_code);
  rte_string_deinit(&text);
  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_return_json(napi_env env,
                                             napi_callback_info info) {
  assert(env);

  rte_string_t status_code;
  rte_string_init(&status_code);

  rte_string_t json_str;
  rte_string_init(&json_str);

  const size_t argc = 5;
  napi_value args[argc];  // this, status_code, json_str, cmd, err_context
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    goto done;
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  if (!rte_nodejs_get_str(env, args[1], &status_code)) {
    goto done;
  }

  if (!rte_nodejs_get_str(env, args[2], &json_str)) {
    goto done;
  }

  json_error_t error;
  json_t* json = json_loads(rte_string_c_str(&json_str), 0, &error);
  if (!json) {
    rte_string_t err_str;
    rte_string_init_with_value(&err_str, "Failed to parse json at %d:%d",
                               error.line, error.column);

    status = napi_throw_error(env, "EINVAL", rte_string_c_str(&err_str));
    ASSERT_IF_NAPI_FAIL(status == napi_ok, "Failed to throw JS exception: %d",
                        status);

    rte_string_deinit(&err_str);

    goto done;
  }

  rte_nodejs_cmd_t* cmd_bridge = NULL;
  status = napi_unwrap(env, args[3], (void**)&cmd_bridge);
  ASSERT_IF_NAPI_FAIL(status == napi_ok && cmd_bridge != NULL,
                      "Failed to get cmd bridge: %d", status);

  rte_error_t err_ctx;
  rte_error_init(&err_ctx);

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  if (!rte_return_json(rte_bridge->c_rte, rte_string_c_str(&status_code), json,
                       cmd_bridge->msg.c_msg, false, NULL)) {
    rte_string_t code_str;
    rte_string_init_with_value(&code_str, "%d", rte_error_last_errno(&err_ctx));

    status = napi_throw_error(env, rte_string_c_str(&code_str),
                              rte_error_last_errmsg(&err_ctx));
    ASSERT_IF_NAPI_FAIL(status == napi_ok, "Failed to throw JS exception: %d",
                        status);

    rte_string_deinit(&code_str);
  }

  rte_error_deinit(&err_ctx);
  json_decref(json);

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  rte_string_deinit(&status_code);
  rte_string_deinit(&json_str);
  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_return_cmd(napi_env env,
                                            napi_callback_info info) {
  assert(env);

  const size_t argc = 4;  // this, status_cmd, request_cmd, err_context
  napi_value args[argc];
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    goto done;
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  rte_nodejs_cmd_t* status_cmd_bridge = NULL;
  status = napi_unwrap(env, args[1], (void**)&status_cmd_bridge);
  GOTO_LABEL_IF_NAPI_FAIL(done, status == napi_ok && status_cmd_bridge != NULL,
                          "Failed to get status command bridge: %d", status);

  rte_nodejs_cmd_t* request_cmd_bridge = NULL;
  status = napi_unwrap(env, args[2], (void**)&request_cmd_bridge);
  GOTO_LABEL_IF_NAPI_FAIL(done, status == napi_ok && request_cmd_bridge != NULL,
                          "Failed to get request command bridge: %d", status);

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  rte_return_cmd(rte_bridge->c_rte, status_cmd_bridge->msg.c_msg,
                 request_cmd_bridge->msg.c_msg, false, NULL);

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_get_manifest(napi_env env,
                                              napi_callback_info info) {
  assert(env);

  rte_string_t name;
  rte_string_init(&name);

  napi_value result = UNDEFINED(env);

  const size_t argc = 2;
  napi_value args[argc];  // this, name
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    goto done;
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  if (!rte_nodejs_get_str(env, args[1], &name)) {
    goto done;
  }

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  rte_value_t* c_manifest_value =
      rte_get_manifest(rte_bridge->c_rte, rte_string_c_str(&name), false);

  if (c_manifest_value) {
    // The returned C value is a clone of the original one, so we don't need to
    // clone it again here.
    result = rte_nodejs_value_wrap(env, c_manifest_value);
  }

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  rte_string_deinit(&name);
  return result;
}

static napi_value rte_nodejs_rte_get_prop(napi_env env,
                                          napi_callback_info info) {
  assert(env);

  rte_string_t name;
  rte_string_init(&name);

  napi_value result = UNDEFINED(env);

  const size_t argc = 2;
  napi_value args[argc];  // this, name
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    goto done;
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  if (!rte_nodejs_get_str(env, args[1], &name)) {
    goto done;
  }

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  rte_value_t* c_prop_value =
      rte_get_prop(rte_bridge->c_rte, rte_string_c_str(&name), false);

  if (c_prop_value) {
    // The returned C value is a clone of the original one, so we don't need to
    // clone it again here.
    result = rte_nodejs_value_wrap(env, c_prop_value);
  }

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  rte_string_deinit(&name);
  return result;
}

static napi_value rte_nodejs_rte_set_prop(napi_env env,
                                          napi_callback_info info) {
  assert(env);

  rte_string_t name;
  rte_string_init(&name);

  const size_t argc = 3;
  napi_value args[argc];  // this, name, value
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    goto done;
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  if (!rte_nodejs_get_str(env, args[1], &name)) {
    goto done;
  }

  rte_nodejs_value_t* value_bridge = NULL;
  status = napi_unwrap(env, args[2], (void**)&value_bridge);
  GOTO_LABEL_IF_NAPI_FAIL(done, status == napi_ok && value_bridge != NULL,
                          "Failed to get value bridge: %d", status);

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  rte_value_t* cloned_value = rte_value_clone(value_bridge->c_value);
  if (!rte_set_prop(rte_bridge->c_rte, rte_string_c_str(&name), cloned_value,
                    false)) {
    rte_value_destroy(cloned_value);
  }

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  rte_string_deinit(&name);
  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_dump_dirty_prop(napi_env env,
                                                 napi_callback_info info) {
  assert(env && info);

  const size_t argc = 1;
  napi_value args[argc];  // this(rte)
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    return UNDEFINED(env);
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  napi_value js_result = NULL;

  rte_string_t* prop_str = rte_string_create();

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  rte_dump_dirty_prop(rte_bridge->c_rte, prop_str);

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

  status = napi_create_string_utf8(env, rte_string_c_str(prop_str),
                                   rte_string_len(prop_str), &js_result);
  rte_string_destroy(prop_str);

  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok,
                                "Failed to create JS string: %d", status);

done:
  return js_result;
}

static napi_value rte_nodejs_rte_is_cmd_connected(napi_env env,
                                                  napi_callback_info info) {
  assert(env && info);

  bool res = false;
  napi_value js_result = NULL;

  const size_t argc = 2;
  napi_value args[argc];  // this, cmd_or_name
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    goto done;
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  napi_valuetype arg_type = 0;
  napi_typeof(env, args[1], &arg_type);

  if (arg_type == napi_string) {
    rte_string_t cmd_name;
    rte_string_init(&cmd_name);

    UNUSED bool status = rte_nodejs_get_str(env, args[1], &cmd_name);
    assert(status);

    RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

    res = rte_is_cmd_connected(rte_bridge->c_rte, rte_string_c_str(&cmd_name));
    rte_string_deinit(&cmd_name);

    RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);
  } else if (arg_type == napi_object) {
    rte_nodejs_cmd_t* cmd_bridge = NULL;
    napi_status status = napi_unwrap(env, args[1], (void**)&cmd_bridge);
    RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && cmd_bridge != NULL,
                                  "Failed to get cmd bridge: %d", status);

    if (cmd_bridge->msg.c_msg == NULL) {
      goto done;
    }

    const char* cmd_name_c_str = rte_msg_get_cmd_name(cmd_bridge->msg.c_msg);

    RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);
    res = rte_is_cmd_connected(rte_bridge->c_rte, cmd_name_c_str);
    RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);
  } else {
    rte_string_t err_str;
    rte_string_init_with_value(&err_str,
                               "Failed to determine if cmd is connected.");

    status = napi_throw_error(env, "EINVAL", rte_string_c_str(&err_str));
    ASSERT_IF_NAPI_FAIL(status == napi_ok, "Failed to throw JS exception: %d",
                        status);

    rte_string_deinit(&err_str);
  }

done:
  status = napi_get_boolean(env, res, &js_result);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok,
                                "Failed to get JS boolean: %d", status);

  return js_result;
}

static napi_value rte_nodejs_rte_extension_unique_name_in_graph(
    napi_env env, napi_callback_info info) {
  assert(env);

  napi_value args[1];  // this
  if (!rte_nodejs_get_args(env, info, args, 1)) {
    return UNDEFINED(env);
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  if (rte_bridge->c_rte == NULL) {
    return UNDEFINED(env);
  }

  rte_extension_t* extension = rte_get_attaching_extension(rte_bridge->c_rte);
  assert(extension && rte_extension_check_integrity(extension));

  rte_string_t* name = &extension->unique_name_in_graph;
  assert(!rte_string_is_empty(name));

  napi_value js_name = NULL;
  status = napi_create_string_utf8(env, rte_string_c_str(name),
                                   rte_string_len(name), &js_name);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok,
                                "Failed to create JS string: %d", status);

  return js_name;
}

static napi_value rte_nodejs_rte_on_init_done(napi_env env,
                                              napi_callback_info info) {
  assert(env);

  const size_t argc = 3;
  napi_value args[argc];  // rte, manifest, property
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    assert(0 && "Should not happen");
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  assert(rte_bridge->c_rte && rte_check_integrity(rte_bridge->c_rte));

  rte_nodejs_metadata_info_t* manifest_bridge = NULL;
  status = napi_unwrap(env, args[1], (void**)&manifest_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && manifest_bridge != NULL,
                                "Failed to get manifest bridge: %d", status);

  rte_nodejs_metadata_info_t* property_bridge = NULL;
  status = napi_unwrap(env, args[2], (void**)&property_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && property_bridge != NULL,
                                "Failed to get property bridge: %d", status);

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  rte_on_init_done(rte_bridge->c_rte, manifest_bridge->c_metadata_info,
                   property_bridge->c_metadata_info);

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  rte_nodejs_metadata_info_invalidate_js(env, args[1]);
  rte_nodejs_metadata_info_invalidate_js(env, args[2]);

  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_on_start_done(napi_env env,
                                               napi_callback_info info) {
  assert(env);

  napi_value args[1];  // rte
  if (!rte_nodejs_get_args(env, info, args, 1)) {
    return UNDEFINED(env);
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  assert(rte_bridge->c_rte && rte_check_integrity(rte_bridge->c_rte));

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  rte_on_start_done(rte_bridge->c_rte);

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_on_stop_done(napi_env env,
                                              napi_callback_info info) {
  assert(env);

  napi_value args[1];  // rte
  if (!rte_nodejs_get_args(env, info, args, 1)) {
    return UNDEFINED(env);
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  assert(rte_bridge->c_rte && rte_check_integrity(rte_bridge->c_rte));

  rte_on_stop_done(rte_bridge->c_rte);

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_on_cmd_done(napi_env env,
                                             napi_callback_info info) {
  assert(env);

  napi_value args[3];  // rte, cmd_id， cmd_name
  if (!rte_nodejs_get_args(env, info, args, 3)) {
    return UNDEFINED(env);
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));
  assert(rte_bridge->c_rte && rte_check_integrity(rte_bridge->c_rte));
  assert(rte_bridge->c_rte->attach_to_type == RTE_ATTACH_TO_EXTENSION);

  rte_string_t cmd_id;
  rte_string_init(&cmd_id);

  rte_string_t cmd_name;
  rte_string_init(&cmd_name);

  if (!rte_nodejs_get_str(env, args[1], &cmd_id)) {
    goto done;
  }

  if (!rte_nodejs_get_str(env, args[2], &cmd_name)) {
    goto done;
  }

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  assert(rte_get_attaching_extension(rte_bridge->c_rte));

  rte_on_cmd_done(rte_bridge->c_rte, &cmd_id, &cmd_name, NULL);

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  rte_string_deinit(&cmd_id);
  rte_string_deinit(&cmd_name);

  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_on_cmd_response_done(napi_env env,
                                                      napi_callback_info info) {
  assert(env);

  napi_value args[4];  // rte, cmd_id， cmd_name, consumed
  if (!rte_nodejs_get_args(env, info, args, 4)) {
    return UNDEFINED(env);
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));
  assert(rte_bridge->c_rte && rte_check_integrity(rte_bridge->c_rte));
  assert(rte_bridge->c_rte->attach_to_type == RTE_ATTACH_TO_EXTENSION);

  rte_string_t cmd_id;
  rte_string_init(&cmd_id);

  rte_string_t cmd_name;
  rte_string_init(&cmd_name);

  bool consumed = true;

  if (!rte_nodejs_get_str(env, args[1], &cmd_id)) {
    goto done;
  }

  if (!rte_nodejs_get_str(env, args[2], &cmd_name)) {
    goto done;
  }

  status = napi_get_value_bool(env, args[3], &consumed);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok,
                                "Failed to get 'JS' result: %d", status);

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  assert(rte_get_attaching_extension(rte_bridge->c_rte));

  rte_on_cmd_response_done(rte_bridge->c_rte, &cmd_id, &cmd_name, consumed,
                           NULL);

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  rte_string_deinit(&cmd_id);
  rte_string_deinit(&cmd_name);

  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_on_deinit_done(napi_env env,
                                                napi_callback_info info) {
  assert(env);
  assert(info);

  const size_t argc = 1;
  napi_value args[argc];  // rte
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    assert(0 && "Should not happen");
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));
  assert(rte_bridge->c_rte && rte_check_integrity(rte_bridge->c_rte));

  RTE_NODEJS_LOCK_RTE_FROM_CLOSE(env, rte_bridge);

  assert(rte_bridge->c_rte && rte_check_integrity(rte_bridge->c_rte));
  rte_on_deinit_done(rte_bridge->c_rte);

  RTE_NODEJS_UNLOCK_RTE_FROM_CLOSE(rte_bridge);

done:
  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_on_delete_extension_done(
    napi_env env, napi_callback_info info) {
  assert(env && info);

  const size_t argc = 1;
  napi_value args[argc];  // rte
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    assert(0 && "Should not happen");
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));
  assert(rte_bridge->c_rte && rte_check_integrity(rte_bridge->c_rte));

  rte_on_delete_extension_done(rte_bridge->c_rte);

  return UNDEFINED(env);
}

static napi_value rte_nodejs_rte_on_create_extension_done(
    napi_env env, napi_callback_info info) {
  assert(env && info);

  const size_t argc = 2;
  napi_value args[argc];  // rte, extensions
  if (!rte_nodejs_get_args(env, info, args, argc)) {
    assert(0 && "Should not happen");
  }

  rte_nodejs_rte_t* rte_bridge = NULL;
  napi_status status = napi_unwrap(env, args[0], (void**)&rte_bridge);
  RETURN_UNDEFINED_IF_NAPI_FAIL(status == napi_ok && rte_bridge != NULL,
                                "Failed to get rte bridge: %d", status);
  assert(rte_bridge && rte_nodejs_rte_check_integrity(rte_bridge, true));
  assert(rte_bridge->c_rte && rte_check_integrity(rte_bridge->c_rte));

  rte_list_t result;
  rte_list_init(&result);

  uint32_t extensions_size = 0;
  status = napi_get_array_length(env, args[1], &extensions_size);
  GOTO_LABEL_IF_NAPI_FAIL(done, status == napi_ok,
                          "Failed to get JS array size: %d", status);

  for (size_t i = 0; i < extensions_size; ++i) {
    napi_value js_extension = NULL;
    status = napi_get_element(env, args[1], i, &js_extension);
    GOTO_LABEL_IF_NAPI_FAIL(done, status == napi_ok,
                            "Failed to get JS array element: %d", status);

    rte_nodejs_extension_t* extension_bridge = NULL;
    status = napi_unwrap(env, js_extension, (void**)&extension_bridge);
    GOTO_LABEL_IF_NAPI_FAIL(done, status == napi_ok && extension_bridge != NULL,
                            "Failed to get extension bridge: %d", status);

    assert(extension_bridge &&
           rte_nodejs_extension_check_integrity(extension_bridge, true));
    assert(extension_bridge->c_extension &&
           rte_extension_check_integrity(extension_bridge->c_extension));

    // Indicate that the extension group hold an ownership of the extension
    // bridge, and must be paired with a dec_rc when the extension group destroy
    // the extension.
    rte_sharedptr_inc_rc(extension_bridge->shared_ptr);

    rte_list_push_ptr_back(&result, extension_bridge->c_extension, NULL);
  }

done:
  rte_on_create_extension_done(rte_bridge->c_rte, result);

  return UNDEFINED(env);
}

napi_value rte_nodejs_rte_module_init(napi_env env, napi_value exports) {
  assert(env && exports);

  EXPORT_FUNC(env, exports, rte_nodejs_rte_register_class);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_send_json);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_send_cmd);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_send_data);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_send_image_frame);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_send_pcm_frame);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_return_text);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_return_json);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_return_cmd);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_get_manifest);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_get_prop);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_set_prop);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_is_cmd_connected);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_extension_unique_name_in_graph);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_dump_dirty_prop);

  EXPORT_FUNC(env, exports, rte_nodejs_rte_on_init_done);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_on_start_done);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_on_stop_done);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_on_cmd_done);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_on_cmd_response_done);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_on_deinit_done);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_on_create_extension_done);
  EXPORT_FUNC(env, exports, rte_nodejs_rte_on_delete_extension_done);

  return exports;
}
