napi_status napi_define_properties(napi_env env,
                                   napi_value object,
                                   size_t property_count,
                                   const napi_property_descriptor* properties) {
  NAPI_PREAMBLE(env);
  if (property_count > 0) {
    CHECK_ARG(env, properties);
  }

  v8::Local<v8::Context> context = env->context();

  v8::Local<v8::Object> obj;
  CHECK_TO_OBJECT(env, context, obj, object);

  for (size_t i = 0; i < property_count; i++) {
    const napi_property_descriptor* p = &properties[i];

    v8::Local<v8::Name> property_name;
    napi_status status =
        v8impl::V8NameFromPropertyDescriptor(env, p, &property_name);

    if (status != napi_ok) {
      return napi_set_last_error(env, status);
    }

    if (p->getter != nullptr || p->setter != nullptr) {
      v8::Local<v8::Value> local_getter;
      v8::Local<v8::Value> local_setter;

      if (p->getter != nullptr) {
        v8::Local<v8::Value> getter_data =
            v8impl::CreateFunctionCallbackData(env, p->getter, p->data);
        CHECK_MAYBE_EMPTY(env, getter_data, napi_generic_failure);

        v8::MaybeLocal<v8::Function> maybe_getter =
            v8::Function::New(context,
                              v8impl::FunctionCallbackWrapper::Invoke,
                              getter_data);
        CHECK_MAYBE_EMPTY(env, maybe_getter, napi_generic_failure);

        local_getter = maybe_getter.ToLocalChecked();
      }
      if (p->setter != nullptr) {
        v8::Local<v8::Value> setter_data =
            v8impl::CreateFunctionCallbackData(env, p->setter, p->data);
        CHECK_MAYBE_EMPTY(env, setter_data, napi_generic_failure);

        v8::MaybeLocal<v8::Function> maybe_setter =
            v8::Function::New(context,
                              v8impl::FunctionCallbackWrapper::Invoke,
                              setter_data);
        CHECK_MAYBE_EMPTY(env, maybe_setter, napi_generic_failure);
        local_setter = maybe_setter.ToLocalChecked();
      }

      v8::PropertyDescriptor descriptor(local_getter, local_setter);
      descriptor.set_enumerable((p->attributes & napi_enumerable) != 0);
      descriptor.set_configurable((p->attributes & napi_configurable) != 0);

      auto define_maybe = obj->DefineProperty(context,
                                              property_name,
                                              descriptor);

      if (!define_maybe.FromMaybe(false)) {
        return napi_set_last_error(env, napi_invalid_arg);
      }
    } else if (p->method != nullptr) {
      v8::Local<v8::Value> cbdata =
          v8impl::CreateFunctionCallbackData(env, p->method, p->data);

      CHECK_MAYBE_EMPTY(env, cbdata, napi_generic_failure);

      v8::MaybeLocal<v8::Function> maybe_fn =
          v8::Function::New(context,
                            v8impl::FunctionCallbackWrapper::Invoke,
                            cbdata);

      CHECK_MAYBE_EMPTY(env, maybe_fn, napi_generic_failure);

      v8::PropertyDescriptor descriptor(maybe_fn.ToLocalChecked(),
                                        (p->attributes & napi_writable) != 0);
      descriptor.set_enumerable((p->attributes & napi_enumerable) != 0);
      descriptor.set_configurable((p->attributes & napi_configurable) != 0);

      auto define_maybe = obj->DefineProperty(context,
                                              property_name,
                                              descriptor);

      if (!define_maybe.FromMaybe(false)) {
        return napi_set_last_error(env, napi_generic_failure);
      }
    } else {
      v8::Local<v8::Value> value = v8impl::V8LocalValueFromJsValue(p->value);

      v8::PropertyDescriptor descriptor(value,
                                        (p->attributes & napi_writable) != 0);
      descriptor.set_enumerable((p->attributes & napi_enumerable) != 0);
      descriptor.set_configurable((p->attributes & napi_configurable) != 0);

      auto define_maybe =
          obj->DefineProperty(context, property_name, descriptor);

      if (!define_maybe.FromMaybe(false)) {
        return napi_set_last_error(env, napi_invalid_arg);
      }
    }
  }

  return GET_RETURN_STATUS(env);
}