#ifndef SRC_PERMISSION_PERMISSION_H_
#define SRC_PERMISSION_PERMISSION_H_

#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

#include "debug_utils.h"
#include "node_diagnostics_channel.h"
#include "node_options.h"
#include "permission/addon_permission.h"
#include "permission/child_process_permission.h"
#include "permission/ffi_permission.h"
#include "permission/fs_permission.h"
#include "permission/inspector_permission.h"
#include "permission/net_permission.h"
#include "permission/permission_base.h"
#include "permission/wasi_permission.h"
#include "permission/worker_permission.h"
#include "v8.h"

#include <string_view>
#include <unordered_map>

namespace node {

class Environment;

namespace fs {
class FSReqBase;
}

namespace permission {

#define THROW_IF_INSUFFICIENT_PERMISSIONS(env, perm, resource, ...)            \
  do {                                                                         \
    node::Environment* env__ = (env);                                          \
    const node::permission::PermissionScope perm__ = (perm);                   \
    const auto resource__ = (resource);                                        \
    if (!env__->permission()->is_granted(env__, perm__, resource__))           \
        [[unlikely]] {                                                         \
      node::permission::Permission::ThrowAccessDenied(                         \
          env__, perm__, resource__);                                          \
      if (!env__->permission()->warning_only()) return __VA_ARGS__;            \
    }                                                                          \
  } while (0)

#define ASYNC_THROW_IF_INSUFFICIENT_PERMISSIONS(                               \
    env, wrap, perm, resource, ...)                                            \
  do {                                                                         \
    node::Environment* env__ = (env);                                          \
    const node::permission::PermissionScope perm__ = (perm);                   \
    const auto resource__ = (resource);                                        \
    if (!env__->permission()->is_granted(env__, perm__, resource__))           \
        [[unlikely]] {                                                         \
      node::permission::Permission::AsyncThrowAccessDenied(                    \
          env__, (wrap), perm__, resource__);                                  \
      if (!env__->permission()->warning_only()) return __VA_ARGS__;            \
    }                                                                          \
  } while (0)

#define ERR_ACCESS_DENIED_IF_INSUFFICIENT_PERMISSIONS(                         \
    env, perm, resource, args, ...)                                            \
  do {                                                                         \
    node::Environment* env__ = (env);                                          \
    const node::permission::PermissionScope perm__ = (perm);                   \
    const auto resource__ = (resource);                                        \
    if (!env__->permission()->is_granted(env__, perm__, resource__))           \
        [[unlikely]] {                                                         \
      Local<Value> err_access;                                                 \
      if (node::permission::CreateAccessDeniedError(env__, perm__, resource__) \
              .ToLocal(&err_access)) {                                         \
        args.GetReturnValue().Set(err_access);                                 \
      } else {                                                                 \
        args.GetReturnValue().Set(UV_EACCES);                                  \
      }                                                                        \
      return __VA_ARGS__;                                                      \
    }                                                                          \
  } while (0)

#define SET_INSUFFICIENT_PERMISSION_ERROR_CALLBACK(scope)                      \
  void InsufficientPermissionError(std::string_view resource) {                \
    v8::HandleScope handle_scope(env()->isolate());                            \
    v8::Context::Scope context_scope(env()->context());                        \
    v8::Local<v8::Value> arg;                                                  \
    if (!permission::CreateAccessDeniedError(env(), (scope), resource)         \
             .ToLocal(&arg)) {                                                 \
    }                                                                          \
    MakeCallback(env()->oncomplete_string(), 1, &arg);                         \
  }

class Permission {
 public:
  Permission();

  FORCE_INLINE bool is_granted(Environment* env,
                               const PermissionScope permission,
                               const std::string_view& res = "") const {
    if (!enabled_) [[likely]] {
      return true;
    }
    return is_scope_granted(env, permission, res);
  }

  FORCE_INLINE bool enabled() const { return enabled_; }

  FORCE_INLINE bool warning_only() const { return warning_only_; }

  static PermissionScope StringToPermission(const std::string& perm);
  static const char* PermissionToString(PermissionScope perm);
  static void ThrowAccessDenied(Environment* env,
                                PermissionScope perm,
                                const std::string_view& res);
  static void AsyncThrowAccessDenied(Environment* env,
                                     fs::FSReqBase* req_wrap,
                                     PermissionScope perm,
                                     const std::string_view& res);

  // CLI Call
  void Apply(Environment* env,
             const std::vector<std::string>& allow,
             PermissionScope scope);
  // Runtime Call
  void Drop(Environment* env,
            PermissionScope scope,
            const std::string_view& param = "");
  void EnablePermissions();
  void EnableWarningOnly();

 private:
  COLD_NOINLINE bool is_scope_granted(Environment* env,
                                      const PermissionScope permission,
                                      const std::string_view& res = "") const;

  BaseObjectPtr<diagnostics_channel::Channel> GetOrCreateChannel(
      Environment* env, PermissionScope scope) const;

  std::unordered_map<PermissionScope, std::shared_ptr<PermissionBase>> nodes_;
  bool enabled_;
  bool warning_only_;
  mutable bool publishing_ = false;
  // Weak refs: BindingData (via BaseObjectPtr) is the sole owner of Channels.
  // Using weak refs here avoids keeping Channels alive past Realm teardown.
  mutable std::unordered_map<PermissionScope,
                             BaseObjectWeakPtr<diagnostics_channel::Channel>>
      channels_;
};

v8::MaybeLocal<v8::Value> CreateAccessDeniedError(Environment* env,
                                                  PermissionScope perm,
                                                  const std::string_view& res);

}  // namespace permission

}  // namespace node

#endif  // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
#endif  // SRC_PERMISSION_PERMISSION_H_
