/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#include <fbjni/fbjni.h>

#include <type_traits>
#include <utility>

namespace facebook::react {

/**
 * A wrapper around a JNI reference (e.g. jni::global_ref<>) that makes it safe
 * to destroy (decrement refcount) from any thread. This wrapping is necessary
 * when we don't have control over the thread where a given JNI reference's
 * destructor will run.
 *
 * In particular, this is needed when a JNI reference is owned by a JSI
 * HostObject or HostFunction, since those may be destroyed on an arbitrary
 * thread (e.g. ther Hermes GC thread) which may not be attached to the JVM.
 * (This is explicitly documented for HostObject, and is the observed behavior
 * in Hermes for HostFunctions.)
 */
template <typename RefT>
class SafeReleaseJniRef {
  using T = std::remove_reference<decltype(*std::declval<RefT>())>::type;

 public:
  /* explicit */ SafeReleaseJniRef(RefT ref) : ref_(std::move(ref)) {}
  SafeReleaseJniRef(const SafeReleaseJniRef& other) = default;
  SafeReleaseJniRef(SafeReleaseJniRef&& other) = default;
  SafeReleaseJniRef& operator=(const SafeReleaseJniRef& other) = default;
  SafeReleaseJniRef& operator=(SafeReleaseJniRef&& other) = default;

  ~SafeReleaseJniRef() {
    if (ref_) {
      jni::ThreadScope ts;
      ref_.reset();
    }
  }

  operator bool() const noexcept {
    return (bool)ref_;
  }

  T& operator*() noexcept {
    return *ref_;
  }

  T* operator->() noexcept {
    return &*ref_;
  }

  const T& operator*() const noexcept {
    return *ref_;
  }

  const T* operator->() const noexcept {
    return &*ref_;
  }

  operator RefT() const {
    return ref_;
  }

 private:
  RefT ref_;
};

} // namespace facebook::react
