#ifndef SRC_NODE_REALM_H_
#define SRC_NODE_REALM_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include <v8.h>
#include <unordered_map>
#include "cleanup_queue.h"
#include "cppgc_helpers.h"
#include "env_properties.h"
#include "memory_tracker.h"
#include "node_snapshotable.h"

namespace node {

struct RealmSerializeInfo {
  std::vector<std::string> builtins;
  std::vector<PropInfo> persistent_values;
  std::vector<PropInfo> native_objects;

  SnapshotIndex context;
  friend std::ostream& operator<<(std::ostream& o, const RealmSerializeInfo& i);
};

using BindingDataStore =
    std::array<BaseObjectWeakPtr<BaseObject>,
               static_cast<size_t>(BindingDataType::kBindingDataTypeCount)>;

/**
 * This is a wrapper around a weak persistent of CppgcMixin, used in the
 * CppgcWrapperList to avoid accessing already garbage collected CppgcMixins.
 */
class CppgcWrapperListNode {
 public:
  explicit inline CppgcWrapperListNode(CppgcMixin* ptr);
  inline explicit operator bool() const { return !persistent; }
  inline CppgcMixin* operator->() const { return persistent.Get(); }
  inline CppgcMixin* operator*() const { return persistent.Get(); }

  cppgc::WeakPersistent<CppgcMixin> persistent;
  // Used by ContainerOf in the ListNode implementation for fast manipulation of
  // CppgcWrapperList.
  ListNode<CppgcWrapperListNode> wrapper_list_node;
};

/**
 * A per-realm list of weak persistent of cppgc wrappers, which implements
 * iterations that require iterate over cppgc wrappers created by Node.js.
 */
class CppgcWrapperList
    : public ListHead<CppgcWrapperListNode,
                      &CppgcWrapperListNode::wrapper_list_node>,
      public MemoryRetainer {
 public:
  void Cleanup();
  void PurgeEmpty();

  SET_MEMORY_INFO_NAME(CppgcWrapperList)
  SET_SELF_SIZE(CppgcWrapperList)
  void MemoryInfo(MemoryTracker* tracker) const override;
};

/**
 * node::Realm is a container for a set of JavaScript objects and functions
 * that associated with a particular global environment.
 *
 * An ECMAScript realm (https://tc39.es/ecma262/#sec-code-realms) representing
 * a global environment in which script is run. Each ECMAScript realm comes
 * with a global object and a set of intrinsic objects. An ECMAScript realm has
 * a [[HostDefined]] field, which contains the node::Realm object.
 *
 * Realm can be a principal realm or a synthetic realm. A principal realm is
 * created with an Environment as its principal global environment to evaluate
 * scripts. A synthetic realm is created with JS APIs like ShadowRealm.
 *
 * Native bindings and builtin modules can be evaluated in either a principal
 * realm or a synthetic realm.
 */
class Realm : public MemoryRetainer {
 public:
  enum Kind {
    kPrincipal,
    kShadowRealm,
  };

  static inline Realm* GetCurrent(v8::Isolate* isolate);
  static inline Realm* GetCurrent(v8::Local<v8::Context> context);
  static inline Realm* GetCurrent(
      const v8::FunctionCallbackInfo<v8::Value>& info);
  template <typename T>
  static inline Realm* GetCurrent(const v8::PropertyCallbackInfo<T>& info);

  Realm(Environment* env, v8::Local<v8::Context> context, Kind kind);

  Realm(const Realm&) = delete;
  Realm& operator=(const Realm&) = delete;
  Realm(Realm&&) = delete;
  Realm& operator=(Realm&&) = delete;

  void MemoryInfo(MemoryTracker* tracker) const override;

  void CreateProperties();
  RealmSerializeInfo Serialize(v8::SnapshotCreator* creator);
  void DeserializeProperties(const RealmSerializeInfo* info);

  v8::MaybeLocal<v8::Value> ExecuteBootstrapper(const char* id);
  v8::MaybeLocal<v8::Value> RunBootstrapping();

  inline void TrackBaseObject(BaseObject* bo);
  inline void UntrackBaseObject(BaseObject* bo);
  inline bool PendingCleanup() const;
  void RunCleanup();

  template <typename T>
  void ForEachBaseObject(T&& iterator) const;

  void PrintInfoForSnapshot();
  void VerifyNoStrongBaseObjects();

  inline IsolateData* isolate_data() const;
  inline Environment* env() const;
  inline v8::Isolate* isolate() const;
  inline Kind kind() const;
  virtual v8::Local<v8::Context> context() const;
  inline bool has_run_bootstrapping_code() const;

  // Methods created using SetMethod(), SetPrototypeMethod(), etc. inside
  // this scope can access the created T* object using
  // GetBindingData<T>(args) later.
  template <typename T, typename... Args>
  T* AddBindingData(v8::Local<v8::Object> target, Args&&... args);
  template <typename T, typename U>
  static inline T* GetBindingData(const v8::PropertyCallbackInfo<U>& info);
  template <typename T>
  static inline T* GetBindingData(
      const v8::FunctionCallbackInfo<v8::Value>& info);
  template <typename T>
  static inline T* GetBindingData(v8::Local<v8::Context> context);
  template <typename T>
  inline T* GetBindingData();
  inline BindingDataStore* binding_data_store();

  // The BaseObject count is a debugging helper that makes sure that there are
  // no memory leaks caused by BaseObjects staying alive longer than expected
  // (in particular, no circular BaseObjectPtr references).
  inline int64_t base_object_count() const;

  // Base object count created after the bootstrap of the realm.
  inline int64_t base_object_created_after_bootstrap() const;

  inline void TrackCppgcWrapper(CppgcMixin* handle);
  inline CppgcWrapperList* cppgc_wrapper_list() { return &cppgc_wrapper_list_; }

#define V(PropertyName, TypeName)                                              \
  virtual v8::Local<TypeName> PropertyName() const = 0;                        \
  virtual void set_##PropertyName(v8::Local<TypeName> value) = 0;
  PER_REALM_STRONG_PERSISTENT_VALUES(V)
#undef V

  std::set<struct node_module*> internal_bindings;
  std::set<std::string> builtins_with_cache;
  std::set<std::string> builtins_without_cache;
  // This is only filled during deserialization. We use a vector since
  // it's only used for tests.
  std::vector<std::string> builtins_in_snapshot;

  // This used during the destruction of cppgc wrappers to inform a GC epilogue
  // callback to clean up the weak persistents used to track cppgc wrappers if
  // the wrappers are already garbage collected to prevent holding on to
  // excessive useless persistents.
  inline void set_should_purge_empty_cppgc_wrappers(bool value) {
    should_purge_empty_cppgc_wrappers_ = value;
  }

 protected:
  ~Realm();

  virtual v8::MaybeLocal<v8::Value> BootstrapRealm() = 0;

  Environment* env_;
  // Shorthand for isolate pointer.
  v8::Isolate* isolate_;
  v8::Global<v8::Context> context_;
  bool should_purge_empty_cppgc_wrappers_ = false;

#define V(PropertyName, TypeName) v8::Global<TypeName> PropertyName##_;
  PER_REALM_STRONG_PERSISTENT_VALUES(V)
#undef V

  static void PurgeEmptyCppgcWrappers(v8::Isolate* isolate,
                                      v8::GCType type,
                                      v8::GCCallbackFlags flags,
                                      void* data);

 private:
  void InitializeContext(v8::Local<v8::Context> context,
                         const RealmSerializeInfo* realm_info);
  void DoneBootstrapping();

  Kind kind_;
  bool has_run_bootstrapping_code_ = false;

  int64_t base_object_count_ = 0;
  int64_t base_object_created_by_bootstrap_ = 0;

  BindingDataStore binding_data_store_;

  BaseObjectList base_object_list_;
  CppgcWrapperList cppgc_wrapper_list_;
};

class PrincipalRealm final : public Realm {
 public:
  PrincipalRealm(Environment* env,
                 v8::Local<v8::Context> context,
                 const RealmSerializeInfo* realm_info);
  ~PrincipalRealm();

  SET_MEMORY_INFO_NAME(PrincipalRealm)
  SET_SELF_SIZE(PrincipalRealm)

#define V(PropertyName, TypeName)                                              \
  v8::Local<TypeName> PropertyName() const override;                           \
  void set_##PropertyName(v8::Local<TypeName> value) override;
  PER_REALM_STRONG_PERSISTENT_VALUES(V)
#undef V

 protected:
  v8::MaybeLocal<v8::Value> BootstrapRealm() override;
};

}  // namespace node

#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#endif  // SRC_NODE_REALM_H_
