/*
 * Copyright 2024 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "jerry-api.h"
#include <emscripten/emscripten.h>

#include <string.h>

#define TYPE_ERROR \
    jerry_create_error(JERRY_ERROR_TYPE, NULL);

#define TYPE_ERROR_ARG \
    jerry_create_error(JERRY_ERROR_TYPE, (const jerry_char_t *)"wrong type of argument");

#define TYPE_ERROR_FLAG \
    jerry_create_error(JERRY_ERROR_TYPE, (const jerry_char_t *)"argument cannot have an error flag");

////////////////////////////////////////////////////////////////////////////////
// Parser and Executor Function
////////////////////////////////////////////////////////////////////////////////

// Note that `is_strict` is currently unsupported by emscripten
jerry_value_t jerry_eval(const jerry_char_t *source_p, size_t source_size, bool is_strict) {
  return (jerry_value_t)EM_ASM_INT({
      // jerry_eval() uses an indirect eval() call,
      // so the global execution context is used.
      // Also see ECMA 5.1 -- 10.4.2 Entering Eval Code.
      var indirectEval = eval;
      try {
        return __jerryRefs.ref(indirectEval(Module.Pointer_stringify($0, $1)));
      } catch (e) {
        var error_ref = __jerryRefs.ref(e);
        __jerryRefs.setError(error_ref, true);
        return error_ref;
      }
    }, source_p, source_size);
}

jerry_value_t jerry_acquire_value(jerry_value_t value) {
  return (jerry_value_t)EM_ASM_INT({
      return __jerryRefs.acquire($0);
    }, value);
}

void jerry_release_value(jerry_value_t value) {
  EM_ASM_INT({
      __jerryRefs.release($0);
    }, value);
}
////////////////////////////////////////////////////////////////////////////////
// Get the global context
////////////////////////////////////////////////////////////////////////////////
jerry_value_t jerry_get_global_object(void) {
  return ((jerry_value_t)EM_ASM_INT_V({ \
      return __jerryRefs.ref(Function('return this;')()); \
    }));
}

jerry_value_t jerry_get_global_builtin(const jerry_char_t *builtin_name) {
  return ((jerry_value_t)EM_ASM_INT({ \
      return __jerryRefs.ref(Function('return this;')()[Module.Pointer_stringify($0)]); \
    }, builtin_name));
}

////////////////////////////////////////////////////////////////////////////////
// Jerry Value Type Checking
////////////////////////////////////////////////////////////////////////////////

#define JERRY_VALUE_HAS_TYPE(ref, typename) \
    ((bool)EM_ASM_INT({ \
             return typeof __jerryRefs.get($0) === (typename); \
           }, (ref)))

#define JERRY_VALUE_IS_INSTANCE(ref, type) \
    ((bool)EM_ASM_INT({ \
             return __jerryRefs.get($0) instanceof (type); \
           }, (ref)))

bool jerry_value_is_array(const jerry_value_t value) {
  return JERRY_VALUE_IS_INSTANCE(value, Array);
}

bool jerry_value_is_boolean(const jerry_value_t value) {
  return JERRY_VALUE_HAS_TYPE(value, 'boolean');
}

bool jerry_value_is_constructor(const jerry_value_t value) {
  return jerry_value_is_function(value);
}

bool jerry_value_is_function(const jerry_value_t value) {
  return JERRY_VALUE_HAS_TYPE(value, 'function');
}

bool jerry_value_is_number(const jerry_value_t value) {
  return JERRY_VALUE_HAS_TYPE(value, 'number');
}

bool jerry_value_is_null(const jerry_value_t value) {
  return ((bool)EM_ASM_INT({
      return __jerryRefs.get($0) === null;
    }, value));
}

bool jerry_value_is_object(const jerry_value_t value) {
  return !jerry_value_is_null(value) &&
          (JERRY_VALUE_HAS_TYPE(value, 'object') || jerry_value_is_function(value));
}

bool jerry_value_is_string(const jerry_value_t value) {
  return JERRY_VALUE_HAS_TYPE(value, 'string');
}

bool jerry_value_is_undefined(const jerry_value_t value) {
  return JERRY_VALUE_HAS_TYPE(value, 'undefined');
}

////////////////////////////////////////////////////////////////////////////////
// Jerry Value Getter Functions
////////////////////////////////////////////////////////////////////////////////

bool jerry_get_boolean_value(const jerry_value_t value) {
  if (!jerry_value_is_boolean(value)) {
    return false;
  }
  return (bool)EM_ASM_INT({
      return (__jerryRefs.get($0) === true);
    }, value);
}

double jerry_get_number_value(const jerry_value_t value) {
  if (!jerry_value_is_number(value)) {
    return 0.0;
  }
  return EM_ASM_DOUBLE({
      return __jerryRefs.get($0);
    }, value);
}
////////////////////////////////////////////////////////////////////////////////
// Functions for UTF-8 encoded string values
////////////////////////////////////////////////////////////////////////////////
jerry_size_t jerry_get_utf8_string_size(const jerry_value_t value) {
  if (!jerry_value_is_string(value)) {
    return 0;
  }
  return (jerry_size_t)EM_ASM_INT({
      return Module.lengthBytesUTF8(__jerryRefs.get($0));
    }, value);
}

jerry_size_t jerry_string_to_utf8_char_buffer(const jerry_value_t value,
                                              jerry_char_t *buffer_p,
                                              jerry_size_t buffer_size) {
  const jerry_size_t str_size = jerry_get_utf8_string_size(value);
  if (str_size == 0 || buffer_size < str_size || buffer_p == NULL) {
    return 0;
  }

  EM_ASM_INT({
      var str = __jerryRefs.get($0);
      // Add one onto the buffer size, since Module.stringToUTF8 adds a null
      // character at the end. This will lead to truncation if we just use
      // buffer_size. Since the actual jerry-api does not do this, we are
      // always careful to allocate space for a null character at the end.
      // Allow stringToUTF8 to write that extra null beyond the passed in
      // buffer_length.
      Module.stringToUTF8(str, $1, $2 + 1);
    }, value, buffer_p, buffer_size);
  return strlen((const char *)buffer_p);
}

////////////////////////////////////////////////////////////////////////////////
// Functions for array object values
////////////////////////////////////////////////////////////////////////////////
uint32_t jerry_get_array_length(const jerry_value_t value) {
  if (!jerry_value_is_array(value)) {
    return 0;
  }
  return (uint32_t)EM_ASM_INT({
      return __jerryRefs.get($0).length;
    }, value);
}

////////////////////////////////////////////////////////////////////////////////
// Jerry Value Creation API
////////////////////////////////////////////////////////////////////////////////
#define JERRY_CREATE_VALUE(value) \
    ((jerry_value_t)EM_ASM_INT_V({ \
        return __jerryRefs.ref((value)); \
      }))

jerry_value_t jerry_create_array(uint32_t size) {
  return (jerry_value_t)EM_ASM_INT({
      return __jerryRefs.ref(new Array($0));
    }, size);
}

jerry_value_t jerry_create_boolean(bool value) {
  return (jerry_value_t)EM_ASM_INT({
      return __jerryRefs.ref(Boolean($0));
    }, value);
}

jerry_value_t jerry_create_error(jerry_error_t error_type, const jerry_char_t *message_p) {
  return jerry_create_error_sz(error_type, message_p, strlen((const char *)message_p));
}

#define JERRY_ERROR(type, msg, sz) (jerry_value_t)(EM_ASM_INT({ \
      return __jerryRefs.ref(new (type)(Module.Pointer_stringify($0, $1))) \
    }, (msg), (sz)))

jerry_value_t jerry_create_error_sz(jerry_error_t error_type,
                                    const jerry_char_t *message_p,
                                    jerry_size_t message_size) {
  jerry_value_t error_ref = 0;
  switch (error_type) {
    case JERRY_ERROR_COMMON:
      error_ref = JERRY_ERROR(Error, message_p, message_size);
      break;
    case JERRY_ERROR_EVAL:
      error_ref = JERRY_ERROR(EvalError, message_p, message_size);
      break;
    case JERRY_ERROR_RANGE:
      error_ref = JERRY_ERROR(RangeError, message_p, message_size);
      break;
    case JERRY_ERROR_REFERENCE:
      error_ref = JERRY_ERROR(ReferenceError, message_p, message_size);
      break;
    case JERRY_ERROR_SYNTAX:
      error_ref = JERRY_ERROR(SyntaxError, message_p, message_size);
      break;
    case JERRY_ERROR_TYPE:
      error_ref = JERRY_ERROR(TypeError, message_p, message_size);
      break;
    case JERRY_ERROR_URI:
      error_ref = JERRY_ERROR(URIError, message_p, message_size);
      break;
    default:
      EM_ASM_INT({
          abort('Cannot create error type: ' + $0);
        }, error_type);
      break;
  }
  jerry_value_set_error_flag(&error_ref);
  return error_ref;
}

jerry_value_t jerry_create_external_function(jerry_external_handler_t handler_p) {
  return (jerry_value_t)EM_ASM_INT({
      return __jerry_create_external_function($0);
    }, handler_p);
}

jerry_value_t jerry_create_number(double value) {
  return (jerry_value_t)EM_ASM_INT({
      return __jerryRefs.ref($0);
    }, value);
}

jerry_value_t jerry_create_number_infinity(bool negative) {
  if (negative) {
    return JERRY_CREATE_VALUE(-Infinity);
  } else {
    return JERRY_CREATE_VALUE(Infinity);
  }
}

jerry_value_t jerry_create_number_nan(void) {
  return JERRY_CREATE_VALUE(NaN);
}

jerry_value_t jerry_create_null(void) {
  return JERRY_CREATE_VALUE(null);
}

jerry_value_t jerry_create_object(void) {
  return JERRY_CREATE_VALUE(new Object());
}

jerry_value_t jerry_create_string(const jerry_char_t *str_p) {
  if (!str_p) {
    return jerry_create_undefined();
  }
  return jerry_create_string_utf8_sz(str_p, strlen((const char *)str_p));
}

jerry_value_t jerry_create_string_sz(const jerry_char_t *str_p, jerry_size_t str_size) {
  return jerry_create_string_utf8_sz(str_p, str_size);
}

jerry_value_t jerry_create_string_utf8(const jerry_char_t *str_p) {
  if (!str_p) {
    return jerry_create_undefined();
  }
  return jerry_create_string_utf8_sz(str_p, strlen((const char *)str_p));
}

jerry_value_t jerry_create_string_utf8_sz(const jerry_char_t *str_p, jerry_size_t str_size) {
  if (!str_p) {
    return jerry_create_undefined();
  }
  return (jerry_value_t)EM_ASM_INT({
      return __jerryRefs.ref(Module.Pointer_stringify($0, $1));
    }, str_p, str_size);
}

jerry_value_t jerry_create_undefined(void) {
  return JERRY_CREATE_VALUE(undefined);
}

////////////////////////////////////////////////////////////////////////////////
// General API Functions of JS Objects
////////////////////////////////////////////////////////////////////////////////

bool jerry_has_property(const jerry_value_t obj_val, const jerry_value_t prop_name_val) {
  if (!jerry_value_is_object(obj_val) || !jerry_value_is_string(prop_name_val)) {
    return false;
  }
  return (bool)EM_ASM_INT({
      var obj = __jerryRefs.get($0);
      var name = __jerryRefs.get($1);
      return (name in obj);
    }, obj_val, prop_name_val);
}

bool jerry_has_own_property(const jerry_value_t obj_val, const jerry_value_t prop_name_val) {
  if (!jerry_value_is_object(obj_val) || !jerry_value_is_string(prop_name_val)) {
    return false;
  }
  return (bool)EM_ASM_INT({
      var obj = __jerryRefs.get($0);
      var name = __jerryRefs.get($1);
      return obj.hasOwnProperty(name);
    }, obj_val, prop_name_val);
}

bool jerry_delete_property(const jerry_value_t obj_val, const jerry_value_t prop_name_val) {
  if (!jerry_value_is_object(obj_val) || !jerry_value_is_string(prop_name_val)) {
    return false;
  }
  return (bool)EM_ASM_INT({
      var obj = __jerryRefs.get($0);
      var name = __jerryRefs.get($1);
      try {
        return delete obj[name];
      } catch (e) {
        // In strict mode, delete throws SyntaxError if the property is an
        // own non-configurable property.
        return false;
      }
      return true;
    }, obj_val, prop_name_val);
}

jerry_value_t jerry_get_property(const jerry_value_t obj_val, const jerry_value_t prop_name_val) {
  if (!jerry_value_is_object(obj_val) || !jerry_value_is_string(prop_name_val)) {
    return TYPE_ERROR_ARG;
  }
  return (jerry_value_t)EM_ASM_INT({
      var obj = __jerryRefs.get($0);
      var name = __jerryRefs.get($1);
      return __jerryRefs.ref(obj[name]);
    }, obj_val, prop_name_val);
}

jerry_value_t jerry_get_property_by_index(const jerry_value_t obj_val, uint32_t index) {
  if (!jerry_value_is_object(obj_val)) {
    return TYPE_ERROR;
  }
  return (jerry_value_t)EM_ASM_INT({
      var obj = __jerryRefs.get($0);
      return __jerryRefs.ref(obj[$1]);
    }, obj_val, index);
}

jerry_value_t jerry_set_property(const jerry_value_t obj_val,
                                 const jerry_value_t prop_name_val,
                                 const jerry_value_t value_to_set) {
  if (jerry_value_has_error_flag(value_to_set) ||
      !jerry_value_is_object(obj_val) ||
      !jerry_value_is_string(prop_name_val)) {
    return TYPE_ERROR_ARG;
  }
  return (jerry_value_t)EM_ASM_INT({
      var obj = __jerryRefs.get($0);
      var name = __jerryRefs.get($1);
      var to_set = __jerryRefs.get($2);
      obj[name] = to_set;
      return __jerryRefs.ref(true);
    }, obj_val, prop_name_val, value_to_set);
}

jerry_value_t jerry_set_property_by_index(const jerry_value_t obj_val,
                                          uint32_t index,
                                          const jerry_value_t value_to_set) {
  if (jerry_value_has_error_flag(value_to_set) ||
      !jerry_value_is_object(obj_val)) {
    return TYPE_ERROR_ARG;
  }
  return (jerry_value_t)EM_ASM_INT({
      var obj = __jerryRefs.get($0);
      var to_set = __jerryRefs.get($2);
      obj[$1] = to_set;
      return __jerryRefs.ref(true);
    }, obj_val, index, value_to_set);
}

void jerry_init_property_descriptor_fields(jerry_property_descriptor_t *prop_desc_p) {
  *prop_desc_p = (jerry_property_descriptor_t) {
    .value = jerry_create_undefined(),
    .getter = jerry_create_undefined(),
    .setter = jerry_create_undefined(),
  };
}

jerry_value_t jerry_define_own_property(const jerry_value_t obj_val,
                                        const jerry_value_t prop_name_val,
                                        const jerry_property_descriptor_t *pdp) {
  if (!jerry_value_is_object(obj_val) && !jerry_value_is_string(obj_val)) {
    return TYPE_ERROR_ARG;
  }
  if ((pdp->is_writable_defined || pdp->is_value_defined)
      && (pdp->is_get_defined || pdp->is_set_defined)) {
    return TYPE_ERROR_ARG;
  }
  if (pdp->is_get_defined && !jerry_value_is_function(pdp->getter)) {
    return TYPE_ERROR_ARG;
  }
  if (pdp->is_set_defined && !jerry_value_is_function(pdp->setter)) {
    return TYPE_ERROR_ARG;
  }

  return (jerry_value_t)(EM_ASM_INT({
      var obj = __jerryRefs.get($12 /* obj_val */);
      var name = __jerryRefs.get($13 /* prop_name_val */);
      var desc = {};
      if ($0 /* is_value_defined */) {
        desc.value = __jerryRefs.get($9);
      }
      if ($1 /* is_get_defined */) {
        desc.get = __jerryRefs.get($10);
      }
      if ($2 /* is_set_defined */) {
        desc.set = __jerryRefs.get($11);
      }
      if ($3 /* is_writable_defined */) {
        desc.writable = Boolean($4 /* is_writable */);
      }
      if ($5 /* is_enumerable_defined */) {
        desc.enumerable = Boolean($6 /* is_enumerable */);
      }
      if ($7 /* is_configurable */) {
        desc.configurable = Boolean($8 /* is_configurable */);
      }

      Object.defineProperty(obj, name, desc);
      return __jerryRefs.ref(Boolean(true));
    }, pdp->is_value_defined,    /* $0 */
       pdp->is_get_defined,      /* $1 */
       pdp->is_set_defined,      /* $2 */
       pdp->is_writable_defined, /* $3 */
       pdp->is_writable,         /* $4 */
       pdp->is_enumerable_defined,   /* $5 */
       pdp->is_enumerable,           /* $6 */
       pdp->is_configurable_defined, /* $7 */
       pdp->is_configurable,         /* $8 */
       pdp->value,   /* $9  */
       pdp->getter,  /* $10 */
       pdp->setter,  /* $11 */
       obj_val,      /* $12 */
       prop_name_val /* $13 */
    ));
}

jerry_value_t emscripten_call_jerry_function(jerry_external_handler_t func_obj_p,
                                             const jerry_value_t func_obj_val,
                                             const jerry_value_t this_val,
                                             const jerry_value_t args_p[],
                                             jerry_size_t args_count) {
  return (func_obj_p)(func_obj_val, this_val, args_p, args_count);
}

jerry_value_t jerry_call_function(const jerry_value_t func_obj_val,
                                  const jerry_value_t this_val,
                                  const jerry_value_t args_p[],
                                  jerry_size_t args_count) {
  if (!jerry_value_is_function(func_obj_val)) {
    return TYPE_ERROR_ARG;
  }

  return (jerry_value_t)EM_ASM_INT({
        var func_obj = __jerryRefs.get($0);
        var this_val = __jerryRefs.get($1);
        var args = [];
        for (var i = 0; i < $3; ++i) {
          args.push(__jerryRefs.get(getValue($2 + i*4, 'i32')));
        }
        try {
          var rv = func_obj.apply(this_val, args);
        } catch (e) {
          var error_ref = __jerryRefs.ref(e);
          __jerryRefs.setError(error_ref, true);
          return error_ref;
        }
        return __jerryRefs.ref(rv);
    }, func_obj_val, this_val, args_p, args_count);
}

jerry_value_t jerry_construct_object(const jerry_value_t func_obj_val,
                                     const jerry_value_t args_p[],
                                     jerry_size_t args_count) {
  if (!jerry_value_is_constructor(func_obj_val)) {
    return TYPE_ERROR_ARG;
  }
  return (jerry_value_t)EM_ASM_INT({
        var func_obj = __jerryRefs.get($0);
        var args = [];
        for (var i = 0; i < $2; ++i) {
          args.push(__jerryRefs.get(getValue($1 + i*4, 'i32')));
        }
        // Call the constructor with new object as `this`
        var bindArgs = [null].concat(args);
        var boundConstructor = func_obj.bind.apply(func_obj, bindArgs);
        var rv = new boundConstructor();
        return __jerryRefs.ref(rv);
    }, func_obj_val, args_p, args_count);
}

jerry_size_t jerry_string_to_char_buffer(const jerry_value_t value,
                                         jerry_char_t *buffer_p,
                                         jerry_size_t buffer_size) {
  return jerry_string_to_utf8_char_buffer(value, buffer_p, buffer_size);
}

jerry_size_t jerry_object_to_string_to_utf8_char_buffer(const jerry_value_t object,
                                                        jerry_char_t *buffer_p,
                                                        jerry_size_t buffer_size) {
  jerry_value_t str_ref = (jerry_value_t)EM_ASM_INT({
    var str = __jerryRefs.ref(String(__jerryRefs.get($0)));
    return str;
  }, object);
  jerry_size_t len = jerry_string_to_utf8_char_buffer(str_ref, buffer_p, buffer_size);
  jerry_release_value(str_ref);

  return len;
}

// FIXME: PBL-43551 Propery CESU-8 => UTF-8 conversion.
jerry_size_t jerry_object_to_string_to_char_buffer(const jerry_value_t object,
                                                   jerry_char_t *buffer_p,
                                                   jerry_size_t buffer_size) {
  return jerry_object_to_string_to_utf8_char_buffer(object,
                                                    buffer_p,
                                                    buffer_size);
}

jerry_value_t jerry_get_object_keys(const jerry_value_t value) {
  if (!jerry_value_is_object(value)) {
    return TYPE_ERROR_ARG;
  }
  return (jerry_value_t)EM_ASM_INT({
      return __jerryRefs.ref(Object.keys(__jerryRefs.get($0)));
    }, value);
}

jerry_value_t jerry_get_prototype(const jerry_value_t value) {
  if (!jerry_value_is_object(value)) {
    return TYPE_ERROR_ARG;
  }
  return (jerry_value_t)EM_ASM_INT({
      return __jerryRefs.ref(__jerryRefs.get($0).prototype);
    }, value);
}

jerry_value_t jerry_set_prototype(const jerry_value_t obj_val, const jerry_value_t proto_obj_val) {
  return 0; // FIXME: Not sure what to do here
}

bool jerry_get_object_native_handle(const jerry_value_t obj_val, uintptr_t *out_handle_p) {
  return EM_ASM_INT({
    var ptr = __jerryRefs.getNativeHandle($0);
    if (ptr === undefined) {
      return false;
    }
    Module.setValue($1, ptr, '*');
    return true;
  }, obj_val, out_handle_p);
}

void jerry_set_object_native_handle(const jerry_value_t obj_val, uintptr_t handle_p,
                                    jerry_object_free_callback_t freecb_p) {
  EM_ASM_INT({
    __jerryRefs.setNativeHandle($0, $1, $2);
  }, obj_val, handle_p, freecb_p);
}

void emscripten_call_jerry_object_free_callback(jerry_object_free_callback_t freecb_p,
                                                uintptr_t handle_p) {
  if (freecb_p) {
    freecb_p(handle_p);
  }
}

////////////////////////////////////////////////////////////////////////////////
// Error flag manipulation functions
////////////////////////////////////////////////////////////////////////////////
//
// The error flag is stored alongside the value in __jerryRefs.
// This allows for us to keep a valid value, like jerryscript does, and be able
// to add / remove a flag specifying whether there was an error or not.

bool jerry_value_has_error_flag(const jerry_value_t value) {
  return (bool)(EM_ASM_INT({
      return __jerryRefs.getError($0);
    }, value));
}

void jerry_value_clear_error_flag(jerry_value_t *value_p) {
  EM_ASM_INT({
      return __jerryRefs.setError($0, false);
    }, *value_p);
}

void jerry_value_set_error_flag(jerry_value_t *value_p) {
  EM_ASM_INT({
      return __jerryRefs.setError($0, true);
    }, *value_p);
}
////////////////////////////////////////////////////////////////////////////////
// Converters of `jerry_value_t`
////////////////////////////////////////////////////////////////////////////////

bool jerry_value_to_boolean(const jerry_value_t value) {
  if (jerry_value_has_error_flag(value)) {
    return false;
  }
  return (bool)EM_ASM_INT({
      return Boolean(__jerryRefs.get($0));
    }, value);
}



jerry_value_t jerry_value_to_number(const jerry_value_t value) {
  if (jerry_value_has_error_flag(value)) {
    return TYPE_ERROR_FLAG;
  }
  return (jerry_value_t)EM_ASM_INT({
      return __jerryRefs.ref(Number(__jerryRefs.get($0)));
    }, value);
}

jerry_value_t jerry_value_to_object(const jerry_value_t value) {
  if (jerry_value_has_error_flag(value)) {
    return TYPE_ERROR_FLAG;
  }
  return (jerry_value_t)EM_ASM_INT({
      return __jerryRefs.ref(new Object(__jerryRefs.get($0)));
    }, value);
}

jerry_value_t jerry_value_to_primitive(const jerry_value_t value) {
  if (jerry_value_has_error_flag(value)) {
    return TYPE_ERROR_FLAG;
  }
  return (jerry_value_t)EM_ASM_INT({
      var val = __jerryRefs.get($0);
      var rv;
      if ((typeof val === 'object' && val != null)
          || (typeof val === 'function')) {
        rv = val.valueOf(); // unbox
      } else {
        rv = val; // already a primitive
      }
      return __jerryRefs.ref(rv);
    }, value);
}

jerry_value_t jerry_value_to_string(const jerry_value_t value) {
  if (jerry_value_has_error_flag(value)) {
    return TYPE_ERROR_FLAG;
  }
  return (jerry_value_t)EM_ASM_INT({
      return __jerryRefs.ref(String(__jerryRefs.get($0)));
    }, value);
}

int jerry_obj_refcount(jerry_value_t o) {
  return EM_ASM_INT({
    try {
      return __jerryRefs.getRefCount($0);
    } catch (e) {
      return 0;
    }
  }, o);
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////

void jerry_init(jerry_init_flag_t flags) {
  EM_ASM(__jerryRefs.reset());
}

void jerry_cleanup(void) {
}