Ctrl K
ComunidadGuías…

include/mgp.hpp

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// Copyright 2026 Memgraph Ltd.
//
// Use of this software is governed by the Business Source License
// included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
// License, and you may not use this file except in compliance with the Business Source License.
//
// As of the Change Date specified in that file, in accordance with
// the Business Source License, use of this software will be governed
// by the Apache License, Version 2.0, included in the file
// licenses/APL.txt.

#pragma once

#include <chrono>
#include <cstdlib>
#include <cstring>
#include <format>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>

#include "_mgp.hpp"
#include "mg_exceptions.hpp"
#include "mg_procedure.h"
#include "text_search_config.hpp"

namespace mgp {

class VectorSearchException : public std::exception {
 public:
  explicit VectorSearchException(std::string message) : message_(std::move(message)) {}

  const char *what() const noexcept override { return message_.c_str(); }

 private:
  std::string message_;
};

class TextSearchException : public std::exception {
 public:
  explicit TextSearchException(std::string message) : message_(std::move(message)) {}

  const char *what() const noexcept override { return message_.c_str(); }

 private:
  std::string message_;
};

class IndexException : public std::exception {
 public:
  explicit IndexException(std::string message) : message_(std::move(message)) {}

  const char *what() const noexcept override { return message_.c_str(); }

 private:
  std::string message_;
};

class ValueException : public std::exception {
 public:
  explicit ValueException(std::string message) : message_(std::move(message)) {}

  const char *what() const noexcept override { return message_.c_str(); }

 private:
  std::string message_;
};

class NotFoundException : public std::exception {
 public:
  explicit NotFoundException(std::string message) : message_(std::move(message)) {}

  const char *what() const noexcept override { return message_.c_str(); }

 private:
  std::string message_;
};

class MustAbortException : public std::exception {
 public:
  explicit MustAbortException(std::string message) : message_(std::move(message)) {}

  const char *what() const noexcept override { return message_.c_str(); }

 private:
  std::string message_;
};

class TerminatedMustAbortException : public MustAbortException {
 public:
  explicit TerminatedMustAbortException() : MustAbortException("Query was asked to terminate directly.") {}
};

class ShutdownMustAbortException : public MustAbortException {
 public:
  explicit ShutdownMustAbortException()
      : MustAbortException("Query was asked to terminate because of server shutdown.") {}
};

class TimeoutMustAbortException : public MustAbortException {
 public:
  explicit TimeoutMustAbortException()
      : MustAbortException("Query was asked to terminate because the timeout was hit.") {}
};

class ExceptionMustAbortException : public MustAbortException {
 public:
  explicit ExceptionMustAbortException()
      : MustAbortException(
            "Query was asked to terminate because an exception occurred. Please contact Memgraph support as this "
            "scenario should not happen!") {}
};

// Forward declarations
class Nodes;
using GraphNodes = Nodes;
class GraphRelationships;
class Relationships;
class Node;
class Relationship;
struct MapItem;
class Duration;
class Point2d;
class Point3d;
class Enum;
class Value;
class QueryExecution;
class ExecutionResult;
class ExecutionHeaders;
class ExecutionRow;

struct StealType {};

inline constexpr StealType steal_type{};

struct RefType {};

inline constexpr RefType ref_type{};

namespace MemoryDispatcher {

extern thread_local std::optional<mgp_memory *> current_memory __attribute__((visibility("default")));

inline mgp_memory *GetMemoryResource() noexcept { return current_memory.value_or(nullptr); }

inline mgp_memory *Register(mgp_memory *mem) noexcept {
  if (current_memory.has_value()) {
    return std::exchange(*current_memory, mem);
  }
  current_memory = mem;
  return nullptr;
}

inline void UnRegister(mgp_memory *mem) noexcept { current_memory = mem; }

inline bool IsThisThreadRegistered() noexcept { return current_memory.has_value(); }
};  // namespace MemoryDispatcher

// Leaving "memory" variable here in order to generate a better module compilation error.
// It should never be used; instead use MemoryDispatcherGuard.
struct UnsupportedMgpMemory {
  UnsupportedMgpMemory() = default;
  void operator=(mgp_memory *) __attribute__((diagnose_if(
      true, "mgp::memory must not be used as of v2.18.1. Please use MemoryDispatcherGuard instead.", "error")));
};

inline UnsupportedMgpMemory memory;  // NOSONAR

class MemoryDispatcherGuard final {
 public:
  explicit MemoryDispatcherGuard(mgp_memory *mem) : old_mem{MemoryDispatcher::Register(mem)} {};

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

  ~MemoryDispatcherGuard() { MemoryDispatcher::UnRegister(old_mem); }

 private:
  mgp_memory *old_mem{nullptr};
};

// Thread must be registered, otherwise the function will segfault.
template <typename Func, typename... Args>
inline decltype(auto) MemHandlerCallback(Func &&func, Args &&...args) {
  return std::forward<Func>(func)(std::forward<Args>(args)..., MemoryDispatcher::GetMemoryResource());
}

/* #region Graph (Id, Graph, Nodes, GraphRelationships, Relationships & Labels) */

/// Wrapper for int64_t IDs to prevent dangerous implicit conversions.
class Id {
 public:
  Id() = default;

  /// Construct Id from uint64_t
  static Id FromUint(uint64_t id);
  /// Construct Id from int64_t
  static Id FromInt(int64_t id);

  int64_t AsInt() const;
  uint64_t AsUint() const;

  bool operator==(const Id &other) const;
  bool operator!=(const Id &other) const;

  bool operator<(const Id &other) const;

 private:
  explicit Id(int64_t id);

  int64_t id_;
};

enum class AbortReason : uint8_t {
  NO_ABORT = 0,

  // transaction has been requested to terminate, ie. "TERMINATE TRANSACTIONS ..."
  TERMINATED = 1,

  // server is gracefully shutting down
  SHUTDOWN = 2,

  // the transaction timeout has been reached. Either via "--query-execution-timeout-sec", or a per-transaction timeout
  TIMEOUT = 3,

  // an exception occurred in the transaction (used for parallel execution)
  EXCEPTION = 4,
};

/// @brief Wrapper class for @ref mgp_graph.
/// This is a Facade over the C API to provide a C++ interface
class Graph {
 private:
  friend class Node;
  friend class Relationship;

 public:
  explicit Graph(mgp_graph *graph);

  /// @brief Returns the graph order (number of nodes).
  int64_t Order() const;
  /// @brief Returns the graph size (number of relationships).
  int64_t Size() const;

  /// @brief Returns an iterable structure of the graph’s nodes.
  GraphNodes Nodes() const;
  /// @brief Returns an iterable structure of the graph’s relationships.
  GraphRelationships Relationships() const;

  /// @brief Returns the graph node with the given ID.
  Node GetNodeById(Id node_id) const;

  /// @brief Returns whether the graph contains a node with the given ID.
  bool ContainsNode(Id node_id) const;
  /// @brief Returns whether the graph contains the given node.
  bool ContainsNode(const Node &node) const;
  /// @brief Returns whether the graph contains a relationship with the given ID.
  bool ContainsRelationship(Id relationship_id) const;
  /// @brief Returns whether the graph contains the given relationship.
  bool ContainsRelationship(const Relationship &relationship) const;

  /// @brief Returns whether the graph is mutable.
  bool IsMutable() const;
  /// @brief Returns whether the graph is in a transactional storage mode.
  bool IsTransactional() const;
  /// @brief Creates a node and adds it to the graph.
  Node CreateNode();
  /// @brief Deletes a node from the graph.
  void DeleteNode(const Node &node);
  /// @brief Deletes a node and all its incident edges from the graph.
  void DetachDeleteNode(const Node &node);
  /// @brief Creates a relationship of type `type` between nodes `from` and `to` and adds it to the graph.
  Relationship CreateRelationship(const Node &from, const Node &to, std::string_view type);
  /// @brief Deletes a relationship from the graph.
  void DeleteRelationship(const Relationship &relationship);

  /// @brief Checks if process must abort
  /// @return AbortReason the reason to abort, if no need to abort then AbortReason::NO_ABORT is returned
  AbortReason MustAbort() const;

  /// @brief Checks if process must abort
  /// @throws MustAbortException If process must abort for any reason
  /// @note For the reason why the process must abort consider using MustAbort method instead
  void CheckMustAbort() const;

 private:
  mgp_graph *graph_;
};

/// @brief View of graph nodes; wrapper class for @ref mgp_vertices_iterator.
class Nodes {
 public:
  explicit Nodes(mgp_vertices_iterator *nodes_iterator);

  class Iterator {
   public:
    friend class Nodes;

    using iterator_category = std::forward_iterator_tag;
    using difference_type = std::ptrdiff_t;
    using value_type = const Node;
    using pointer = value_type *;
    using reference = value_type &;

    explicit Iterator(std::shared_ptr<mgp_vertices_iterator> nodes_iterator);

    Iterator(const Iterator &other);
    Iterator &operator=(const Iterator &other) = delete;

    ~Iterator() = default;

    Iterator &operator++();

    Iterator operator++(int);

    bool operator==(Iterator other) const;
    bool operator!=(Iterator other) const;

    Node operator*() const;

   private:
    std::shared_ptr<mgp_vertices_iterator> nodes_iterator_;
    size_t index_ = 0;
  };

  Iterator begin() const;
  Iterator end() const;

  Iterator cbegin() const;
  Iterator cend() const;

 private:
  std::shared_ptr<mgp_vertices_iterator> nodes_iterator_;
};

/// @brief View of graph relationships.
// NB: Necessary because of the MGP API not having a method that returns a mgp_edges_iterator over all graph
// relationships.
/// This is a Facade over the C API to provide a C++ interface
class GraphRelationships {
 public:
  explicit GraphRelationships(mgp_graph *graph);

  class Iterator {
   public:
    friend class GraphRelationships;

    using iterator_category = std::forward_iterator_tag;
    using difference_type = std::ptrdiff_t;
    using value_type = const Relationship;
    using pointer = value_type *;
    using reference = value_type &;

    explicit Iterator(mgp_vertices_iterator *nodes_iterator);

    Iterator(const Iterator &other);
    Iterator &operator=(const Iterator &other) = delete;

    ~Iterator();

    Iterator &operator++();
    Iterator operator++(int);

    bool operator==(Iterator other) const;
    bool operator!=(Iterator other) const;

    Relationship operator*() const;

   private:
    mgp_vertices_iterator *nodes_iterator_ = nullptr;
    mgp_edges_iterator *out_relationships_iterator_ = nullptr;
    size_t index_ = 0;
  };

  Iterator begin() const;
  Iterator end() const;

  Iterator cbegin() const;
  Iterator cend() const;

 private:
  mgp_graph *graph_;
};

/// @brief Wrapper class for @ref mgp_edges_iterator.
class Relationships {
 public:
  explicit Relationships(mgp_edges_iterator *relationships_iterator);

  class Iterator {
   public:
    friend class Relationships;

    using iterator_category = std::forward_iterator_tag;
    using difference_type = std::ptrdiff_t;
    using value_type = const Relationship;
    using pointer = value_type *;
    using reference = value_type &;

    explicit Iterator(std::shared_ptr<mgp_edges_iterator> relationships_iterator);

    Iterator(const Iterator &other);
    Iterator &operator=(const Iterator &other) = delete;

    ~Iterator() = default;

    Iterator &operator++();
    Iterator operator++(int);

    bool operator==(Iterator other) const;
    bool operator!=(Iterator other) const;

    Relationship operator*() const;

   private:
    std::shared_ptr<mgp_edges_iterator> relationships_iterator_{};
    size_t index_ = 0;
  };

  Iterator begin() const;
  Iterator end() const;

  Iterator cbegin() const;
  Iterator cend() const;

 private:
  std::shared_ptr<mgp_edges_iterator> relationships_iterator_{};
};

/// @brief View of node labels.
class Labels {
 public:
  explicit Labels(mgp_vertex *node_ptr);

  Labels(const Labels &other);
  Labels(Labels &&other) noexcept;

  Labels &operator=(const Labels &other);
  Labels &operator=(Labels &&other) noexcept;

  ~Labels();

  /// @brief Returns the number of the labels, i.e. the size of their list.
  size_t Size() const;

  /// @brief Return the node’s label at position `index`.
  std::string_view operator[](size_t index) const;

  class Iterator {
   private:
    friend class Labels;

   public:
    using value_type = Labels;
    using difference_type = std::ptrdiff_t;
    using pointer = const Labels *;
    using reference = const Labels &;
    using iterator_category = std::forward_iterator_tag;

    bool operator==(const Iterator &other) const;

    bool operator!=(const Iterator &other) const;

    Iterator &operator++();

    std::string_view operator*() const;

   private:
    Iterator(const Labels *iterable, size_t index);

    const Labels *iterable_;
    size_t index_;
  };

  Iterator begin();
  Iterator end();

  Iterator cbegin();
  Iterator cend();

 private:
  mgp_vertex *node_ptr_;
};

/* #endregion */

/* #region Types */

/* #region Containers (List, Map) */

/// @brief Wrapper class for @ref mgp_list.
class List {
 private:
  friend class Value;
  friend class Record;
  friend class Result;
  friend class Parameter;

 public:
  /// @brief Creates a List from the copy of the given @ref mgp_list.
  explicit List(mgp_list *ptr);
  /// @brief Creates a List from the given @ref mgp_list and takes ownership of it.
  explicit List(mgp_list *ptr, StealType);
  /// @brief Creates a List from the copy of the given @ref mgp_list.
  explicit List(const mgp_list *const_ptr);

  /// @brief Creates an empty List.
  explicit List();

  /// @brief Creates a List with the given `capacity`.
  explicit List(size_t capacity);

  /// @brief Creates a List from the given vector.
  explicit List(const std::vector<Value> &values);
  /// @brief Creates a List from the given vector.
  explicit List(std::vector<Value> &&values);

  /// @brief Creates a List from the given initializer_list.
  explicit List(std::initializer_list<Value> list);

  List(const List &other);
  List(List &&other) noexcept;

  List &operator=(const List &other);
  List &operator=(List &&other) noexcept;

  ~List();

  /// @brief Returns wheter the list contains any deleted values.
  bool ContainsDeleted() const;

  /// @brief Returns the size of the list.
  size_t Size() const;
  /// @brief Returns whether the list is empty.
  bool Empty() const;

  /// @brief Returns the value at the given `index`.
  // TODO: this is returning a reference wrapper to the value inside
  //       the list, problem is that we loose const correctness
  Value operator[](size_t index) const;

  ///@brief Same as above, but non const value
  Value operator[](size_t index);

  class Iterator {
   private:
    friend class List;

   public:
    using value_type = List;
    using difference_type = std::ptrdiff_t;
    using pointer = const List *;
    using reference = const List &;
    using iterator_category = std::forward_iterator_tag;

    bool operator==(const Iterator &other) const;

    bool operator!=(const Iterator &other) const;

    Iterator &operator++();

    Value operator*() const;

   private:
    Iterator(const List *iterable, size_t index);

    const List *iterable_;
    size_t index_;
  };

  Iterator begin() const;
  Iterator end() const;

  Iterator cbegin() const;
  Iterator cend() const;

  /// @brief Appends the given `value` to the list. The `value` is copied.
  void Append(const Value &value);

  /// @brief Extends the list and appends the given `value` to it. The `value` is copied.
  void AppendExtend(const Value &value);

  /// @brief Ensure underlying capacity is at least `n`
  void Reserve(size_t n);

  // Value Pop();  // not implemented (requires mgp_list_pop in the MGP API):

  /// @exception std::runtime_error List contains value of unknown type.
  bool operator==(const List &other) const;
  /// @exception std::runtime_error List contains value of unknown type.
  bool operator!=(const List &other) const;

  /// @brief returns the string representation
  std::string ToString() const;

  /// @brief returns the mgp_list pointer
  mgp_list *GetPtr() const;

 private:
  mgp_list *ptr_;
};

/// @brief Wrapper class for @ref mgp_map.
class Map {
 private:
  friend class Value;
  friend class Record;
  friend class Result;
  friend class Parameter;
  friend class QueryExecution;

 public:
  /// @brief Creates a Map from the copy of the given @ref mgp_map.
  explicit Map(mgp_map *ptr);

  /// @brief Creates a Map from the given @ref mgp_map and takes ownership of it.
  explicit Map(mgp_map *ptr, StealType);

  /// @brief Creates a Map from the copy of the given @ref mgp_map.
  explicit Map(const mgp_map *const_ptr);

  /// @brief Creates an empty Map.
  explicit Map();

  /// @brief Creates a Map from the given vector.
  explicit Map(const std::map<std::string_view, Value> &items);

  /// @brief Creates a Map from the given vector.
  explicit Map(std::map<std::string_view, Value> &&items);

  /// @brief Creates a Map from the given initializer_list (map items correspond to initializer list pairs).
  Map(std::initializer_list<std::pair<std::string_view, Value>> items);

  Map(const Map &other);
  Map(Map &&other) noexcept;

  Map &operator=(const Map &other);
  Map &operator=(Map &&other) noexcept;

  ~Map();

  /// @brief Returns wheter the map contains any deleted values.
  bool ContainsDeleted() const;

  /// @brief Returns the size of the map.
  size_t Size() const;

  /// @brief Returns whether the map is empty.
  bool Empty() const;

  /// @brief Returns the value at the given `key`.
  // TODO: this is returning a reference wrapper to the value inside
  //       the map, problem is that we loose const correctness
  Value operator[](std::string_view key) const;

  /// @brief Returns the value at the given `key`.
  Value operator[](std::string_view key);

  /// @brief Returns the value at the given `key`.
  Value At(std::string_view key) const;

  /// @brief Returns true if the given `key` exists.
  bool KeyExists(std::string_view key) const;

  class Iterator {
   public:
    friend class Map;

    using iterator_category = std::forward_iterator_tag;
    using difference_type = std::ptrdiff_t;
    using value_type = const MapItem;
    using pointer = value_type *;
    using reference = value_type &;

    explicit Iterator(mgp_map_items_iterator *map_items_iterator);

    Iterator(const Iterator &other);
    Iterator &operator=(const Iterator &other) = delete;

    ~Iterator();

    Iterator &operator++();
    Iterator operator++(int);

    bool operator==(Iterator other) const;
    bool operator!=(Iterator other) const;

    MapItem operator*() const;

   private:
    mgp_map_items_iterator *map_items_iterator_ = nullptr;
  };

  Iterator begin() const;
  Iterator end() const;

  Iterator cbegin() const;
  Iterator cend() const;

  /// @brief Inserts the given `key`-`value` pair into the map. The `value` is copied.
  void Insert(std::string_view key, const Value &value);

  /// @brief Updates the `key`-`value` pair in the map. If the key doesn't exist, the value gets inserted. The `value`
  /// is copied.
  void Update(std::string_view key, const Value &value);

  /// @brief Erases the element associated with the key from the map, if it doesn't exist does nothing.
  void Erase(std::string_view key);

  // void Clear();  // not implemented (requires mgp_map_clear in the MGP API)

  /// @exception std::runtime_error Map contains value of unknown type.
  bool operator==(const Map &other) const;

  /// @exception std::runtime_error Map contains value of unknown type.
  bool operator!=(const Map &other) const;

  /// @brief returns the string representation
  std::string ToString() const;

 private:
  mgp_map *ptr_;
};

/* #endregion */

/* #region Graph elements (Node, Relationship & Path) */
/// @brief Wrapper class for @ref mgp_vertex.
class Node {
 public:
  friend class Graph;
  friend class Path;
  friend class Value;
  friend class Record;
  friend class Result;
  friend class Parameter;

  /// @brief Creates a Node from the copy of the given @ref mgp_vertex.
  explicit Node(mgp_vertex *ptr);

  /// @brief Creates a Node from the copy of the given @ref mgp_vertex.
  explicit Node(const mgp_vertex *const_ptr);

  /// @brief returns the mgp_vertex pointer
  mgp_vertex *GetPtr() const;

  Node(const Node &other);
  Node(Node &&other) noexcept;

  Node &operator=(const Node &other);
  Node &operator=(Node &&other) noexcept;

  ~Node();

  /// @brief Returns wheter the node has been deleted.
  bool IsDeleted() const;

  /// @brief Returns the node’s ID.
  mgp::Id Id() const;

  /// @brief Returns an iterable & indexable structure of the node’s labels.
  mgp::Labels Labels() const;

  /// @brief Returns whether the node has the given `label`.
  bool HasLabel(std::string_view label) const;

  /// @brief Returns an std::map of the node’s properties.
  std::unordered_map<std::string, Value> Properties() const;

  /// @brief Sets the chosen property to the given value.
  void SetProperty(std::string property, Value value);

  /// @brief Sets the chosen properties to the given values.
  void SetProperties(std::unordered_map<std::string_view, Value> properties);

  /// @brief Removes the chosen property.
  void RemoveProperty(std::string property);

  /// @brief Retrieves the value of the chosen property.
  Value GetProperty(const std::string &property) const;

  /// @brief Returns an iterable structure of the node’s inbound relationships.
  Relationships InRelationships() const;

  /// @brief Returns an iterable structure of the node’s outbound relationships.
  Relationships OutRelationships() const;

  /// @brief Adds a label to the node.
  void AddLabel(std::string_view label);

  /// @brief Removes a label from the node.
  void RemoveLabel(std::string_view label);

  bool operator<(const Node &other) const;

  /// @exception std::runtime_error Node properties contain value(s) of unknown type.
  bool operator==(const Node &other) const;

  /// @exception std::runtime_error Node properties contain value(s) of unknown type.
  bool operator!=(const Node &other) const;

  /// @brief returns the string representation
  std::string ToString() const;

  /// @brief returns the in degree of a node
  inline size_t InDegree() const;

  /// @brief returns the out degree of a node
  inline size_t OutDegree() const;

 private:
  mgp_vertex *ptr_;
};

/// @brief Wrapper class for @ref mgp_edge.
class Relationship {
 private:
  friend class Graph;
  friend class Path;
  friend class Value;
  friend class Record;
  friend class Result;
  friend class Parameter;

 public:
  /// @brief Creates a Relationship from the copy of the given @ref mgp_edge.
  explicit Relationship(mgp_edge *ptr);
  /// @brief Creates a Relationship from the copy of the given @ref mgp_edge.
  explicit Relationship(const mgp_edge *const_ptr);

  Relationship(const Relationship &other);
  Relationship(Relationship &&other) noexcept;

  Relationship &operator=(const Relationship &other);
  Relationship &operator=(Relationship &&other) noexcept;

  ~Relationship();

  /// @brief Returns wheter the relationship has been deleted.
  bool IsDeleted() const;

  /// @brief Returns the relationship’s ID.
  mgp::Id Id() const;

  /// @brief Returns the relationship’s type.
  std::string_view Type() const;

  /// @brief Returns an std::map of the relationship’s properties.
  std::unordered_map<std::string, Value> Properties() const;

  /// @brief Sets the chosen property to the given value.
  void SetProperty(std::string property, Value value);

  /// @brief Sets the chosen properties to the given values.
  void SetProperties(std::unordered_map<std::string_view, Value> properties);

  /// @brief Removes the chosen property.
  void RemoveProperty(std::string property);

  /// @brief Retrieves the value of the chosen property.
  Value GetProperty(const std::string &property) const;

  /// @brief Returns the relationship’s source node.
  Node From() const;
  /// @brief Returns the relationship’s destination node.
  Node To() const;

  bool operator<(const Relationship &other) const;

  /// @exception std::runtime_error Relationship properties contain value(s) of unknown type.
  bool operator==(const Relationship &other) const;
  /// @exception std::runtime_error Relationship properties contain value(s) of unknown type.
  bool operator!=(const Relationship &other) const;

  /// @brief returns the string representation
  std::string ToString() const;

 private:
  mgp_edge *ptr_;
};

/// @brief Wrapper class for @ref mgp_path.
class Path {
 private:
  friend class Value;
  friend class Record;
  friend class Result;
  friend class Parameter;

 public:
  /// @brief Creates a Path from the copy of the given @ref mgp_path.
  explicit Path(mgp_path *ptr);
  /// @brief Creates a Path from the copy of the given @ref mgp_path.
  explicit Path(const mgp_path *const_ptr);

  /// @brief Creates a Path starting with the given `start_node`.
  explicit Path(const Node &start_node);

  Path(const Path &other);
  Path(Path &&other) noexcept;

  Path &operator=(const Path &other);
  Path &operator=(Path &&other) noexcept;

  ~Path();

  /// @brief Returns wheter the path contains any deleted values.
  bool ContainsDeleted() const;

  /// Returns the path length (number of relationships).
  size_t Length() const;

  /// @brief Returns the node at the given `index`.
  /// @pre The `index` must be less than or equal to length of the path.
  Node GetNodeAt(size_t index) const;

  /// @brief Returns the relationship at the given `index`.
  /// @pre The `index` must be less than length of the path.
  Relationship GetRelationshipAt(size_t index) const;

  /// @brief Adds a relationship continuing from the last node on the path.
  void Expand(const Relationship &relationship);
  /// @brief Removes the last node and the last relationship from the path.
  void Pop();

  /// @exception std::runtime_error Path contains element(s) with unknown value.
  bool operator==(const Path &other) const;
  /// @exception std::runtime_error Path contains element(s) with unknown value.
  bool operator!=(const Path &other) const;

  /// @brief returns the string representation
  std::string ToString() const;

 private:
  mgp_path *ptr_;
};

/* #endregion */

/* #region Temporal types (Date, LocalTime, LocalDateTime, Duration) */

/// @brief Wrapper class for @ref mgp_date.
class Date {
 private:
  friend class Duration;
  friend class Value;
  friend class Record;
  friend class Result;
  friend class Parameter;

 public:
  /// @brief Creates a Date object from the copy of the given @ref mgp_date.
  explicit Date(mgp_date *ptr);
  /// @brief Creates a Date object from the copy of the given @ref mgp_date.
  explicit Date(const mgp_date *const_ptr);

  /// @brief Creates a Date object from the given string representing a date in the ISO 8601 format (`YYYY-MM-DD`,
  /// `YYYYMMDD`, or `YYYY-MM`).
  explicit Date(std::string_view string);

  /// @brief Creates a Date object with the given `year`, `month`, and `day` properties.
  Date(int year, int month, int day);

  Date(const Date &other);
  Date(Date &&other) noexcept;

  Date &operator=(const Date &other);
  Date &operator=(Date &&other) noexcept;

  ~Date();

  /// @brief Returns the current Date.
  static Date Now();

  /// @brief Returns the date’s year property.
  int Year() const;
  /// @brief Returns the date’s month property.
  int Month() const;
  /// @brief Returns the date’s day property.
  int Day() const;

  /// @brief Returns the date’s timestamp (microseconds from the Unix epoch).
  int64_t Timestamp() const;

  bool operator==(const Date &other) const;
  Date operator+(const Duration &dur) const;
  Date operator-(const Duration &dur) const;
  Duration operator-(const Date &other) const;

  bool operator<(const Date &other) const;

  /// @brief returns the string representation
  std::string ToString() const;

 private:
  mgp_date *ptr_;
};

/// @brief Wrapper class for @ref mgp_local_time.
class LocalTime {
 private:
  friend class Duration;
  friend class Value;
  friend class Record;
  friend class Result;
  friend class Parameter;

 public:
  /// @brief Creates a LocalTime object from the copy of the given @ref mgp_local_time.
  explicit LocalTime(mgp_local_time *ptr);
  /// @brief Creates a LocalTime object from the copy of the given @ref mgp_local_time.
  explicit LocalTime(const mgp_local_time *const_ptr);

  /// @brief Creates a LocalTime object from the given string representing a date in the ISO 8601 format ([T]hh:mm:ss,
  /// `[T]hh:mm`, `[T]hhmmss`, `[T]hhmm`, or `[T]hh`).
  explicit LocalTime(std::string_view string);

  /// @brief Creates a LocalTime object with the given `hour`, `minute`, `second`, `millisecond`, and `microsecond`
  /// properties.
  LocalTime(int hour, int minute, int second, int millisecond, int microsecond);

  LocalTime(const LocalTime &other);
  LocalTime(LocalTime &&other) noexcept;

  LocalTime &operator=(const LocalTime &other);
  LocalTime &operator=(LocalTime &&other) noexcept;

  ~LocalTime();

  /// @brief Returns the current LocalTime.
  static LocalTime Now();

  /// @brief Returns the object’s `hour` property.
  int Hour() const;
  /// @brief Returns the object’s `minute` property.
  int Minute() const;
  /// @brief Returns the object’s `second` property.
  int Second() const;
  /// @brief Returns the object’s `millisecond` property.
  int Millisecond() const;
  /// @brief Returns the object’s `microsecond` property.
  int Microsecond() const;

  /// @brief Returns the object’s timestamp (microseconds from the Unix epoch).
  int64_t Timestamp() const;

  bool operator==(const LocalTime &other) const;
  LocalTime operator+(const Duration &dur) const;
  LocalTime operator-(const Duration &dur) const;
  Duration operator-(const LocalTime &other) const;

  bool operator<(const LocalTime &other) const;

  /// @brief returns the string representation
  std::string ToString() const;

 private:
  mgp_local_time *ptr_;
};

/// @brief Wrapper class for @ref mgp_local_date_time.
class LocalDateTime {
 private:
  friend class Duration;
  friend class Value;
  friend class Record;
  friend class Result;
  friend class Parameter;

 public:
  /// @brief Creates a LocalDateTime object from the copy of the given @ref mgp_local_date_time.
  explicit LocalDateTime(mgp_local_date_time *ptr);
  /// @brief Creates a LocalDateTime object from the copy of the given @ref mgp_local_date_time.
  explicit LocalDateTime(const mgp_local_date_time *const_ptr);

  /// @brief Creates a LocalDateTime object from the given string representing a date in the ISO 8601 format
  /// (`YYYY-MM-DDThh:mm:ss`, `YYYY-MM-DDThh:mm`, `YYYYMMDDThhmmss`, `YYYYMMDDThhmm`, or `YYYYMMDDThh`).
  explicit LocalDateTime(std::string_view string);

  /// @brief Creates a LocalDateTime object with the given `year`, `month`, `day`, `hour`, `minute`, `second`,
  /// `millisecond`, and `microsecond` properties.
  LocalDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond);

  LocalDateTime(const LocalDateTime &other);
  LocalDateTime(LocalDateTime &&other) noexcept;

  LocalDateTime &operator=(const LocalDateTime &other);
  LocalDateTime &operator=(LocalDateTime &&other) noexcept;

  ~LocalDateTime();

  /// @brief Returns the current LocalDateTime.
  static LocalDateTime Now();

  /// @brief Returns the object’s `year` property.
  int Year() const;
  /// @brief Returns the object’s `month` property.
  int Month() const;
  /// @brief Returns the object’s `day` property.
  int Day() const;
  /// @brief Returns the object’s `hour` property.
  int Hour() const;
  /// @brief Returns the object’s `minute` property.
  int Minute() const;
  /// @brief Returns the object’s `second` property.
  int Second() const;
  /// @brief Returns the object’s `millisecond` property.
  int Millisecond() const;
  /// @brief Returns the object’s `microsecond` property.
  int Microsecond() const;

  /// @brief Returns the object’s timestamp (microseconds from the Unix epoch).
  int64_t Timestamp() const;

  bool operator==(const LocalDateTime &other) const;
  LocalDateTime operator+(const Duration &dur) const;
  LocalDateTime operator-(const Duration &dur) const;
  Duration operator-(const LocalDateTime &other) const;

  bool operator<(const LocalDateTime &other) const;

  /// @brief returns the string representation
  std::string ToString() const;

 private:
  mgp_local_date_time *ptr_;
};

/// @brief Wrapper class for @ref mgp_duration.
class Duration {
 private:
  friend class Date;
  friend class LocalTime;
  friend class LocalDateTime;
  friend class ZonedDateTime;
  friend class Value;
  friend class Record;
  friend class Result;
  friend class Parameter;

 public:
  /// @brief Creates a Duration from the copy of the given @ref mgp_duration.
  explicit Duration(mgp_duration *ptr);
  /// @brief Creates a Duration from the copy of the given @ref mgp_duration.
  explicit Duration(const mgp_duration *const_ptr);

  /// @brief Creates a Duration object from the given string in the following format: `P[nD]T[nH][nM][nS]`, where (1)
  /// `n` stands for a number, (2) capital letters are used as a separator, (3) each field in `[]` is optional, and (4)
  /// only the last field may be a non-integer.
  explicit Duration(std::string_view string);

  /// @brief Creates a Duration object from the given number of microseconds.
  explicit Duration(int64_t microseconds);

  /// @brief Creates a Duration object with the given `day`, `hour`, `minute`, `second`, `millisecond`, and
  /// `microsecond` properties.
  Duration(double day, double hour, double minute, double second, double millisecond, double microsecond);

  Duration(const Duration &other);
  Duration(Duration &&other) noexcept;

  Duration &operator=(const Duration &other);
  Duration &operator=(Duration &&other) noexcept;

  ~Duration();

  /// @brief Returns the duration as microseconds.
  int64_t Microseconds() const;

  bool operator==(const Duration &other) const;
  Duration operator+(const Duration &other) const;
  Duration operator-(const Duration &other) const;
  Duration operator-() const;

  bool operator<(const Duration &other) const;

  /// @brief returns the string representation
  std::string ToString() const;

 private:
  mgp_duration *ptr_;
};

// @brief Wrapper class for @ref mgp_zoned_date_time.
class ZonedDateTime {
 private:
  friend class Duration;
  friend class Value;
  friend class Record;
  friend class Result;
  friend class Parameter;

 public:
  /// @brief Creates a ZonedDateTime object from the copy of the given @ref mgp_zoned_date_time.
  explicit ZonedDateTime(mgp_zoned_date_time *ptr);
  /// @brief Creates a ZonedDateTime object from the copy of the given @ref mgp_zoned_date_time.
  explicit ZonedDateTime(const mgp_zoned_date_time *const_ptr);

  /// @brief Creates a ZonedDateTime object from the given string representing a date in the ISO 8601 format
  /// (`YYYY-MM-DDThh:mm:ss`, `YYYY-MM-DDThh:mm`, `YYYYMMDDThhmmss`, `YYYYMMDDThhmm`, or `YYYYMMDDThh`).
  explicit ZonedDateTime(std::string_view string);

  /// @brief Creates a ZonedDateTime object with the given `year`, `month`, `day`, `hour`, `minute`, `second`,
  /// `millisecond`, `microsecond`, and `offset_in_minutes` properties.
  ZonedDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond,
                int offset_in_minutes);

  /// @brief Creates a ZonedDateTime object with the given `year`, `month`, `day`, `hour`, `minute`, `second`,
  /// `millisecond`, `microsecond`, and `timezone_name` properties.
  ZonedDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond, int microsecond,
                std::string_view timezone_name);

  ZonedDateTime(const ZonedDateTime &other);
  ZonedDateTime(ZonedDateTime &&other) noexcept;

  ZonedDateTime &operator=(const ZonedDateTime &other);
  ZonedDateTime &operator=(ZonedDateTime &&other) noexcept;

  ~ZonedDateTime();

  /// @brief Returns the current ZonedDateTime.
  static ZonedDateTime Now();

  /// @brief Returns the object’s `year` property.
  int Year() const;
  /// @brief Returns the object’s `month` property.
  int Month() const;
  /// @brief Returns the object’s `day` property.
  int Day() const;
  /// @brief Returns the object’s `hour` property.
  int Hour() const;
  /// @brief Returns the object’s `minute` property.
  int Minute() const;
  /// @brief Returns the object’s `second` property.
  int Second() const;
  /// @brief Returns the object’s `millisecond` property.
  int Millisecond() const;
  /// @brief Returns the object’s `microsecond` property.
  int Microsecond() const;
  /// @brief Returns the object’s `timezone` string property.
  char const *Timezone() const;
  /// @brief Returns the object’s `offset` property.
  int Offset() const;

  /// @brief Returns the object's timestamp (microseconds from the Unix epoch).
  int64_t Timestamp() const;

  bool operator==(const ZonedDateTime &other) const;
  ZonedDateTime operator+(const Duration &dur) const;
  ZonedDateTime operator-(const Duration &dur) const;
  Duration operator-(const ZonedDateTime &other) const;

  bool operator<(const ZonedDateTime &other) const;

  /// @brief returns the string representation
  std::string ToString() const;

 private:
  mgp_zoned_date_time *ptr_;
};

/* #endregion */

/* #region Point2d */

/// @brief Wrapper class for @ref mgp_point_2d.
class Point2d {
 private:
  friend class Value;
  friend class Record;
  friend class Result;

 public:
  /// @brief Creates a Point2d object from the copy of the given @ref mgp_point_2d.
  explicit Point2d(mgp_point_2d *ptr);
  /// @brief Creates a Point2d object from the copy of the given @ref mgp_point_2d.
  explicit Point2d(const mgp_point_2d *const_ptr);

  /// @brief Creates a Point2d from components.
  Point2d(double x, double y, uint16_t srid);

  Point2d(const Point2d &other);
  Point2d(Point2d &&other) noexcept;

  Point2d &operator=(const Point2d &other);
  Point2d &operator=(Point2d &&other) noexcept;

  ~Point2d();

  /// @brief Returns the x coordinate.
  double X() const;
  /// @brief Returns the y coordinate.
  double Y() const;
  /// @brief Returns the SRID.
  uint16_t Srid() const;

  bool operator==(const Point2d &other) const;
  bool operator!=(const Point2d &other) const;

  std::string ToString() const;

 private:
  mgp_point_2d *ptr_;
};

/* #endregion */

/* #region Point3d */

/// @brief Wrapper class for @ref mgp_point_3d.
class Point3d {
 private:
  friend class Value;
  friend class Record;
  friend class Result;

 public:
  /// @brief Creates a Point3d object from the copy of the given @ref mgp_point_3d.
  explicit Point3d(mgp_point_3d *ptr);
  /// @brief Creates a Point3d object from the copy of the given @ref mgp_point_3d.
  explicit Point3d(const mgp_point_3d *const_ptr);

  /// @brief Creates a Point3d from components.
  Point3d(double x, double y, double z, uint16_t srid);

  Point3d(const Point3d &other);
  Point3d(Point3d &&other) noexcept;

  Point3d &operator=(const Point3d &other);
  Point3d &operator=(Point3d &&other) noexcept;

  ~Point3d();

  /// @brief Returns the x coordinate.
  double X() const;
  /// @brief Returns the y coordinate.
  double Y() const;
  /// @brief Returns the z coordinate.
  double Z() const;
  /// @brief Returns the SRID.
  uint16_t Srid() const;

  bool operator==(const Point3d &other) const;
  bool operator!=(const Point3d &other) const;

  std::string ToString() const;

 private:
  mgp_point_3d *ptr_;
};

/* #endregion */

/* #region Enum */

/// @brief Wrapper class for @ref mgp_enum.
class Enum {
 private:
  friend class Value;
  friend class Record;
  friend class Result;

 public:
  /// @brief Creates an Enum object from the copy of the given @ref mgp_enum.
  explicit Enum(mgp_enum *ptr);
  /// @brief Creates an Enum object from the copy of the given @ref mgp_enum.
  explicit Enum(const mgp_enum *const_ptr);

  /// @brief Creates an Enum from type name and value name.
  Enum(std::string_view type_name, std::string_view value_name);

  Enum(const Enum &other);
  Enum(Enum &&other) noexcept;

  Enum &operator=(const Enum &other);
  Enum &operator=(Enum &&other) noexcept;

  ~Enum();

  /// @brief Returns the type name.
  std::string_view TypeName() const;
  /// @brief Returns the value name.
  std::string_view ValueName() const;

  bool operator==(const Enum &other) const;
  bool operator!=(const Enum &other) const;

  std::string ToString() const;

 private:
  mgp_enum *ptr_;
};

/* #endregion */

/* #endregion */

/* #region Value */
enum class Type : uint8_t {
  Null,
  Any,
  Bool,
  Int,
  Double,
  String,
  List,
  Map,
  Node,
  Relationship,
  Path,
  Date,
  LocalTime,
  LocalDateTime,
  Duration,
  ZonedDateTime,
  Point2d,
  Point3d,
  Enum
};

/// @brief Wrapper class for @ref mgp_value.
class Value {
 public:
  friend class List;
  friend class Map;
  friend class Date;
  friend class LocalTime;
  friend class LocalDateTime;
  friend class Duration;
  friend class Point2d;
  friend class Point3d;
  friend class Enum;
  friend class Record;
  friend class Result;

  explicit Value(mgp_value *ptr);

  explicit Value(StealType /*steal*/, mgp_value *ptr);

  explicit Value(RefType /*ref*/, mgp_value *ptr);

  // Null constructor:
  explicit Value();

  // Primitive type constructors:
  explicit Value(bool value);
  explicit Value(int64_t value);
  explicit Value(double value);

  // String constructors:
  explicit Value(const char *value);
  explicit Value(std::string_view value);
  // Container constructors:

  /// @brief Constructs a List value from the copy of the given `list`.
  explicit Value(const List &list);
  /// @note The behavior of accessing `list` after performing this operation is undefined.
  explicit Value(List &&list);

  /// @brief Constructs a Map value from the copy of the given `map`.
  explicit Value(const Map &map);
  /// @brief Constructs a Map value and takes ownership of the given `map`.
  /// @note The behavior of accessing `map` after performing this operation is undefined.
  explicit Value(Map &&map);

  // Graph element type constructors:

  /// @brief Constructs a Node value from the copy of the given `node`.
  explicit Value(const Node &node);
  /// @brief Constructs a Node value and takes ownership of the given `node`.
  /// @note The behavior of accessing `node` after performing this operation is undefined.
  explicit Value(Node &&node);

  /// @brief Constructs a Relationship value from the copy of the given `node`.
  explicit Value(const Relationship &relationship);
  /// @brief Constructs a Relationship value and takes ownership of the given `relationship`.
  /// @note The behavior of accessing `relationship` after performing this operation is undefined.
  explicit Value(Relationship &&relationship);

  /// @brief Constructs a Path value from the copy of the given `path`.
  explicit Value(const Path &path);
  /// @brief Constructs a Path value and takes ownership of the given `path`.
  /// @note The behavior of accessing `path` after performing this operation is undefined.
  explicit Value(Path &&path);

  // Temporal type constructors:

  /// @brief Constructs a Date value from the copy of the given `date`.
  explicit Value(const Date &date);
  /// @brief Constructs a Date value and takes ownership of the given `path`.
  /// @note The behavior of accessing `date` after performing this operation is undefined.
  explicit Value(Date &&date);

  /// @brief Constructs a LocalTime value from the copy of the given `local_time`.
  explicit Value(const LocalTime &local_time);
  /// @brief Constructs a LocalTime value and takes ownership of the given `local_time`.
  /// @note The behavior of accessing `local_time` after performing this operation is undefined.
  explicit Value(LocalTime &&local_time);

  /// @brief Constructs a LocalDateTime value from the copy of the given `local_date_time`.
  explicit Value(const LocalDateTime &local_date_time);
  /// @brief Constructs a LocalDateTime value and takes ownership of the given `local_date_time`.
  /// @note The behavior of accessing `local_date_time` after performing this operation is undefined.
  explicit Value(LocalDateTime &&local_date_time);

  /// @brief Constructs a Duration value from the copy of the given `duration`.
  explicit Value(const Duration &duration);
  /// @brief Constructs a Duration value and takes ownership of the given `duration`.
  /// @note The behavior of accessing `duration` after performing this operation is undefined.
  explicit Value(Duration &&duration);

  /// @brief Constructs a ZonedDateTime value from the copy of the given `zoned_date_time`.
  explicit Value(const ZonedDateTime &zoned_date_time);
  /// @brief Constructs a ZonedDateTime value and takes ownership of the given `zoned_date_time`.
  /// @note The behavior of accessing `zoned_date_time` after performing this operation is undefined.
  explicit Value(ZonedDateTime &&zoned_date_time);

  /// @brief Constructs a Point2d value from the copy of the given `point`.
  explicit Value(const Point2d &point);
  /// @brief Constructs a Point2d value and takes ownership of the given `point`.
  explicit Value(Point2d &&point);

  /// @brief Constructs a Point3d value from the copy of the given `point`.
  explicit Value(const Point3d &point);
  /// @brief Constructs a Point3d value and takes ownership of the given `point`.
  explicit Value(Point3d &&point);

  /// @brief Constructs an Enum value from the copy of the given `enum_v`.
  explicit Value(const Enum &enum_v);
  /// @brief Constructs an Enum value and takes ownership of the given `enum_v`.
  explicit Value(Enum &&enum_v);

  Value(const Value &other);
  Value(Value &&other) noexcept;

  Value &operator=(const Value &other);
  Value &operator=(Value &&other) noexcept;

  bool IsRef() const;

  ~Value();

  /// @brief Returns the pointer to the stored value with the least significat bit set to 0.
  mgp_value *ptr() const;

  /// @brief Returns the type of the value.
  /// @exception std::runtime_error The value type is unknown.
  mgp::Type Type() const;

  /// @pre Value type needs to be Type::Bool.
  bool ValueBool() const;
  bool ValueBool();
  /// @pre Value type needs to be Type::Int.
  int64_t ValueInt() const;
  int64_t ValueInt();
  /// @pre Value type needs to be Type::Double.
  double ValueDouble() const;
  double ValueDouble();
  /// @pre Value type needs to be Type::Numeric.
  double ValueNumeric() const;
  double ValueNumeric();
  /// @pre Value type needs to be Type::String.
  std::string_view ValueString() const;
  std::string_view ValueString();
  /// @pre Value type needs to be Type::List.
  List ValueList() const;
  List ValueList();
  /// @pre Value type needs to be Type::Map.
  Map ValueMap() const;
  Map ValueMap();
  /// @pre Value type needs to be Type::Node.
  Node ValueNode() const;
  Node ValueNode();
  /// @pre Value type needs to be Type::Relationship.
  Relationship ValueRelationship() const;
  Relationship ValueRelationship();
  /// @pre Value type needs to be Type::Path.
  Path ValuePath() const;
  Path ValuePath();
  /// @pre Value type needs to be Type::Date.
  Date ValueDate() const;
  Date ValueDate();
  /// @pre Value type needs to be Type::LocalTime.
  LocalTime ValueLocalTime() const;
  LocalTime ValueLocalTime();
  /// @pre Value type needs to be Type::LocalDateTime.
  LocalDateTime ValueLocalDateTime() const;
  LocalDateTime ValueLocalDateTime();
  /// @pre Value type needs to be Type::Duration.
  Duration ValueDuration() const;
  Duration ValueDuration();
  /// @pre Value type needs to be Type::LocalDateTime.
  ZonedDateTime ValueZonedDateTime() const;
  ZonedDateTime ValueZonedDateTime();
  /// @pre Value type needs to be Type::Point2d.
  Point2d ValuePoint2d() const;
  Point2d ValuePoint2d();
  /// @pre Value type needs to be Type::Point3d.
  Point3d ValuePoint3d() const;
  Point3d ValuePoint3d();
  /// @pre Value type needs to be Type::Enum.
  Enum ValueEnum() const;
  Enum ValueEnum();

  /// @brief Returns whether the value is null.
  bool IsNull() const;
  /// @brief Returns whether the value is boolean.
  bool IsBool() const;
  /// @brief Returns whether the value is an integer.
  bool IsInt() const;
  /// @brief Returns whether the value is a floating-point number.
  bool IsDouble() const;
  /// @brief Returns whether the value is numeric.
  bool IsNumeric() const;
  /// @brief Returns whether the value is a string.
  bool IsString() const;
  /// @brief Returns whether the value is a @ref List.
  bool IsList() const;
  /// @brief Returns whether the value is a @ref Map.
  bool IsMap() const;
  /// @brief Returns whether the value is a @ref Node.
  bool IsNode() const;
  /// @brief Returns whether the value is a @ref Relationship.
  bool IsRelationship() const;
  /// @brief Returns whether the value is a @ref Path.
  bool IsPath() const;
  /// @brief Returns whether the value is a @ref Date object.
  bool IsDate() const;
  /// @brief Returns whether the value is a @ref LocalTime object.
  bool IsLocalTime() const;
  /// @brief Returns whether the value is a @ref LocalDateTime object.
  bool IsLocalDateTime() const;
  /// @brief Returns whether the value is a @ref Duration object.
  bool IsDuration() const;
  /// @brief Returns whether the value is a @ref ZonedDateTime object.
  bool IsZonedDateTime() const;
  /// @brief Returns whether the value is a @ref Point2d object.
  bool IsPoint2d() const;
  /// @brief Returns whether the value is a @ref Point3d object.
  bool IsPoint3d() const;
  /// @brief Returns whether the value is an @ref Enum object.
  bool IsEnum() const;

  /// @exception std::runtime_error Unknown value type.
  bool operator==(const Value &other) const;
  /// @exception std::runtime_error Unknown value type.
  bool operator!=(const Value &other) const;

  bool operator<(const Value &other) const;

  friend std::ostream &operator<<(std::ostream &os, const mgp::Value &value);

  /// @brief returns the string representation
  std::string ToString() const;

 private:
  /// least signifact bit signifies whether the value is a reference type
  mgp_value *ptr_;
};

/// @brief Key-value pair representing @ref Map items.
struct MapItem {
  const std::string_view key;
  const Value value;

  bool operator==(MapItem &other) const;
  bool operator!=(MapItem &other) const;

  bool operator<(const MapItem &other) const;
};

/* #endregion */

/* #region Results */

/// @brief Procedure result class
class Record {
 public:
  explicit Record(mgp_result_record *record);

  /// @brief Inserts a boolean value under field `field_name`.
  void Insert(const char *field_name, bool value);
  /// @brief Inserts an integer value under field `field_name`.
  void Insert(const char *field_name, std::int64_t value);
  /// @brief Inserts a floating-point value under field `field_name`.
  void Insert(const char *field_name, double value);
  /// @brief Inserts a string value under field `field_name`.
  void Insert(const char *field_name, std::string_view value);
  /// @brief Inserts a string value under field `field_name`.
  void Insert(const char *field_name, const char *value);
  /// @brief Inserts a @ref List value under field `field_name`.
  void Insert(const char *field_name, const List &list);
  /// @brief Inserts a @ref Map value under field `field_name`.
  void Insert(const char *field_name, const Map &map);
  /// @brief Inserts a @ref Node value under field `field_name`.
  void Insert(const char *field_name, const Node &node);
  /// @brief Inserts a @ref Relationship value under field `field_name`.
  void Insert(const char *field_name, const Relationship &relationship);
  /// @brief Inserts a @ref Path value under field `field_name`.
  void Insert(const char *field_name, const Path &path);
  /// @brief Inserts a @ref Date value under field `field_name`.
  void Insert(const char *field_name, const Date &date);
  /// @brief Inserts a @ref LocalTime value under field `field_name`.
  void Insert(const char *field_name, const LocalTime &local_time);
  /// @brief Inserts a @ref LocalDateTime value under field `field_name`.
  void Insert(const char *field_name, const LocalDateTime &local_date_time);
  /// @brief Inserts a @ref Duration value under field `field_name`.
  void Insert(const char *field_name, const Duration &duration);
  /// @brief Inserts a @ref ZonedDateTime value under field `field_name`.
  void Insert(const char *field_name, const ZonedDateTime &zoned_date_time);
  /// @brief Inserts a @ref Point2d value under field `field_name`.
  void Insert(const char *field_name, const Point2d &point);
  /// @brief Inserts a @ref Point3d value under field `field_name`.
  void Insert(const char *field_name, const Point3d &point);
  /// @brief Inserts an @ref Enum value under field `field_name`.
  void Insert(const char *field_name, const Enum &enum_v);
  /// @brief Inserts a @ref Value value under field `field_name`, and then call appropriate insert.
  void Insert(const char *field_name, const Value &value);

 private:
  mgp_result_record *record_;
};

/// @brief Factory class for @ref Record
class RecordFactory {
 public:
  explicit RecordFactory(mgp_result *result);

  Record NewRecord() const;

  void SetErrorMessage(std::string_view error_msg) const;

  void SetErrorMessage(const char *error_msg) const;

 private:
  mgp_result *result_;
};

/// @brief Function result class
/// This is a Facade over the C API to provide a C++ interface
class Result {
 public:
  explicit Result(mgp_func_result *result);

  /// @brief Sets a null value to be returned.
  inline void SetValue();
  /// @brief Sets a boolean value to be returned.
  inline void SetValue(bool value);
  /// @brief Sets an integer value to be returned.
  inline void SetValue(std::int64_t value);
  /// @brief Sets a floating-point value to be returned.
  inline void SetValue(double value);
  /// @brief Sets a string value to be returned.
  inline void SetValue(std::string_view value);
  /// @brief Sets a string value to be returned.
  inline void SetValue(const char *value);
  /// @brief Sets a @ref List value to be returned.
  inline void SetValue(const List &list);
  /// @brief Sets a @ref List value to be returned.
  inline void SetValue(List &&list);
  /// @brief Sets a @ref Map value to be returned.
  inline void SetValue(const Map &map);
  /// @brief Sets a @ref Map value to be returned.
  inline void SetValue(Map &&map);
  /// @brief Sets a @ref Node value to be returned.
  inline void SetValue(const Node &node);
  /// @brief Sets a @ref Relationship value to be returned.
  inline void SetValue(const Relationship &relationship);
  /// @brief Sets a @ref Path value to be returned.
  inline void SetValue(const Path &path);
  /// @brief Sets a @ref Date value to be returned.
  inline void SetValue(const Date &date);
  /// @brief Sets a @ref LocalTime value to be returned.
  inline void SetValue(const LocalTime &local_time);
  /// @brief Sets a @ref LocalDateTime value to be returned.
  inline void SetValue(const LocalDateTime &local_date_time);
  /// @brief Sets a @ref Duration value to be returned.
  inline void SetValue(const Duration &duration);
  /// @brief Sets a @ref ZonedDateTime value to be returned.
  inline void SetValue(const ZonedDateTime &zoned_date_time);
  /// @brief Sets a @ref Point2d value to be returned.
  inline void SetValue(const Point2d &point);
  /// @brief Sets a @ref Point3d value to be returned.
  inline void SetValue(const Point3d &point);
  /// @brief Sets an @ref Enum value to be returned.
  inline void SetValue(const Enum &enum_v);
  /// @brief Sets an arbitrary @ref Value to be returned.
  inline void SetValue(const Value &value);

  void SetErrorMessage(std::string_view error_msg) const;

  void SetErrorMessage(const char *error_msg) const;

 private:
  mgp_func_result *result_;
};

/* #endregion */

/* #region Module */

/// @brief Represents a procedure’s parameter. Parameters are defined by their name, type, and (if optional) default
/// value.
class Parameter {
 public:
  std::string_view name;
  Type type_;
  Type list_item_type_;

  bool optional = false;
  Value default_value;

  /// @brief Creates a non-optional parameter with the given `name` and `type`.
  Parameter(std::string_view name, Type type);

  /// @brief Creates an optional boolean parameter with the given `name` and `default_value`.
  Parameter(std::string_view name, Type type, bool default_value);

  /// @brief Creates an optional integer parameter with the given `name` and `default_value`.
  Parameter(std::string_view name, Type type, int64_t default_value);

  /// @brief Creates an optional floating-point parameter with the given `name` and `default_value`.
  Parameter(std::string_view name, Type type, double default_value);

  /// @brief Creates an optional string parameter with the given `name` and `default_value`.
  Parameter(std::string_view name, Type type, std::string_view default_value);

  /// @brief Creates an optional string parameter with the given `name` and `default_value`.
  Parameter(std::string_view name, Type type, const char *default_value);

  /// @brief Creates an optional parameter with the given `name` and `default_value`.
  Parameter(std::string_view name, Type type, Value default_value);

  /// @brief Creates a non-optional ListParameter with the given `name` and `item_type`.
  Parameter(std::string_view name, std::pair<Type, Type> list_type);

  /// @brief Creates an optional List parameter with the given `name`, `item_type`, and `default_value`.
  Parameter(std::string_view name, std::pair<Type, Type> list_type, Value default_value);

  mgp_type *GetMGPType() const;
};

/// @brief Represents a procedure’s return value. The values are defined by their name and type.
class Return {
 public:
  std::string_view name;
  Type type_;
  Type list_item_type_;

  /// @brief Creates a return value with the given `name` and `type`.
  Return(std::string_view name, Type type);

  Return(std::string_view name, std::pair<Type, Type> list_type);

  mgp_type *GetMGPType() const;
};

class ExecutionHeaders {
 public:
  ExecutionHeaders(mgp_execution_headers *headers);
  size_t Size() const;
  std::string At(size_t index) const;

  std::string_view operator[](size_t index) const;

  class Iterator {
   private:
    friend class ExecutionHeaders;

   public:
    using value_type = ExecutionHeaders;
    using difference_type = std::ptrdiff_t;
    using pointer = const ExecutionHeaders *;
    using reference = const ExecutionHeaders &;
    using iterator_category = std::forward_iterator_tag;

    bool operator==(const Iterator &other) const;

    bool operator!=(const Iterator &other) const;

    Iterator &operator++();

    std::string_view operator*() const;

   private:
    Iterator(const ExecutionHeaders *iterable, size_t index);

    const ExecutionHeaders *iterable_;
    size_t index_;
  };

  Iterator begin();
  Iterator end();

  Iterator cbegin();
  Iterator cend();

 private:
  mgp_execution_headers *headers_;
};

class QueryExecution {
 public:
  QueryExecution(mgp_graph *graph);
  ExecutionResult ExecuteQuery(std::string_view query, Map params = Map()) const;
  ExecutionResult ExecuteQuery(std::string query, Map params = Map()) const;

 private:
  mgp_graph *graph_;
};

class ExecutionRow {
 private:
  Map row_;

 public:
  ExecutionRow(mgp_map *row);

  /// @brief Returns the size of the map.
  size_t Size() const;

  /// @brief Returns whether the map is empty.
  bool Empty() const;

  /// @brief Returns the value at the given `key`.
  Value operator[](std::string_view key) const;

  /// @brief Returns the value at the given `key`.
  Value At(std::string_view key) const;

  /// @brief Returns true if the given `key` exists.
  bool KeyExists(std::string_view key) const;

  mgp::Map Values() const;
};

class ExecutionResult {
 public:
  ExecutionResult(mgp_execution_result *result, mgp_graph *graph);
  ~ExecutionResult();
  ExecutionHeaders Headers() const;
  std::optional<ExecutionRow> PullOne() const;

 private:
  mgp_execution_result *result_;
  mgp_graph *graph_;
};

enum class ProcedureType : uint8_t {
  Read,
  Write,
};

/// @brief Adds a procedure to the query module.
/// @param callback - procedure callback
/// @param name - procedure name
/// @param proc_type - procedure type (read/write)
/// @param parameters - procedure parameters
/// @param returns - procedure return values
/// @param module - the query module that the procedure is added to
/// @param memory - access to memory
inline void AddProcedure(mgp_proc_cb callback, std::string_view name, ProcedureType proc_type,
                         std::vector<Parameter> parameters, std::vector<Return> returns, mgp_module *module,
                         mgp_memory *memory);

/// @brief Adds a batch procedure to the query module.
/// @param callback - procedure callback
/// @param initializer - procedure initializer
/// @param cleanup - procedure cleanup
/// @param name - procedure name
/// @param proc_type - procedure type (read/write)
/// @param parameters - procedure parameters
/// @param returns - procedure return values
/// @param module - the query module that the procedure is added to
/// @param memory - access to memory
inline void AddBatchProcedure(mgp_proc_cb callback, mgp_proc_initializer initializer, mgp_proc_cleanup cleanup,
                              std::string_view name, ProcedureType proc_type, std::vector<Parameter> parameters,
                              std::vector<Return> returns, mgp_module *module, mgp_memory *memory);

/// @brief Adds a function to the query module.
/// @param callback - function callback
/// @param name - function name
/// @param parameters - function parameters
/// @param module - the query module that the function is added to
/// @param memory - access to memory
inline void AddFunction(mgp_func_cb callback, std::string_view name, std::vector<Parameter> parameters,
                        mgp_module *module, mgp_memory *memory);

/* #endregion */

namespace util {
inline uint64_t Fnv(const std::string_view s) {
  // fnv1a is recommended so use it as the default implementation.
  uint64_t hash = 14695981039346656037UL;

  for (const auto &ch : s) {
    hash = (hash ^ (uint64_t)ch) * 1099511628211UL;
  }

  return hash;
}

/**
 * Does FNV-like hashing on a collection. Not truly FNV
 * because it operates on 8-bit elements, while this
 * implementation uses size_t elements (collection item
 * hash).
 *
 * https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
 *
 *
 * @tparam TIterable A collection type that has begin() and end().
 * @tparam TElement Type of element in the collection.
 * @tparam THash Hash type (has operator() that accepts a 'const TEelement &'
 *  and returns size_t. Defaults to std::hash<TElement>.
 * @param iterable A collection of elements.
 * @param element_hash Function for hashing a single element.
 * @return The hash of the whole collection.
 */
template <typename TIterable, typename TElement, typename THash = std::hash<TElement>>
struct FnvCollection {
  size_t operator()(const TIterable &iterable) const {
    uint64_t hash = 14695981039346656037u;
    THash element_hash;
    for (const TElement &element : iterable) {
      hash *= fnv_prime;
      hash ^= element_hash(element);
    }
    return hash;
  }

 private:
  static const uint64_t fnv_prime = 1099511628211u;
};

/**
 * Like FNV hashing for a collection, just specialized for two elements to avoid
 * iteration overhead.
 */
template <typename TA, typename TB, typename TAHash = std::hash<TA>, typename TBHash = std::hash<TB>>
struct HashCombine {
  size_t operator()(const TA &a, const TB &b) const {
    static constexpr size_t fnv_prime = 1099511628211UL;
    static constexpr size_t fnv_offset = 14695981039346656037UL;
    size_t ret = fnv_offset;
    ret ^= TAHash()(a);
    ret *= fnv_prime;
    ret ^= TBHash()(b);
    return ret;
  }
};

/**
 * Like FNV hashing for a collection, just specialized for three elements to avoid
 * iteration overhead.
 */
template <typename TA, typename TB, typename TC, typename TAHash = std::hash<TA>, typename TBHash = std::hash<TB>,
          typename TCHash = std::hash<TC>>
struct HashCombine3 {
  size_t operator()(const TA &a, const TB &b, const TC &c) const {
    static constexpr size_t fnv_prime = 1099511628211UL;
    static constexpr size_t fnv_offset = 14695981039346656037UL;
    size_t ret = fnv_offset;
    ret ^= TAHash()(a);
    ret *= fnv_prime;
    ret ^= TBHash()(b);
    ret *= fnv_prime;
    ret ^= TCHash()(c);
    return ret;
  }
};

// uint to int conversion in C++ is a bit tricky. Take a look here
// https://stackoverflow.com/questions/14623266/why-cant-i-reinterpret-cast-uint-to-int
// for more details.
template <typename TDest, typename TSrc>
TDest MemcpyCast(TSrc src) {
  TDest dest;
  static_assert(sizeof(dest) == sizeof(src), "MemcpyCast expects source and destination to be of the same size");
  static_assert(std::is_arithmetic_v<TSrc>, "MemcpyCast expects source to be an arithmetic type");
  static_assert(std::is_arithmetic_v<TDest>, "MemcpyCast expects destination to be an arithmetic type");
  std::memcpy(&dest, &src, sizeof(src));
  return dest;
}

/// @brief Returns whether two MGP API values are equal.
inline bool ValuesEqual(mgp_value *value1, mgp_value *value2);

/// @brief Returns whether two MGP API lists are equal.
inline bool ListsEqual(mgp_list *list1, mgp_list *list2) {
  if (list1 == list2) {
    return true;
  }
  if (mgp::list_size(list1) != mgp::list_size(list2)) {
    return false;
  }
  const size_t len = mgp::list_size(list1);
  for (size_t i = 0; i < len; ++i) {
    if (!util::ValuesEqual(mgp::list_at(list1, i), mgp::list_at(list2, i))) {
      return false;
    }
  }
  return true;
}

/// @brief Returns whether two MGP API maps are equal.
inline bool MapsEqual(mgp_map *map1, mgp_map *map2) {
  if (map1 == map2) {
    return true;
  }
  const size_t size = mgp::map_size(map1);
  if (size != mgp::map_size(map2)) {
    return false;
  }
  if (size == 0) {
    return true;
  }
  // The sizes agree, so every key of map1 resolving in map2 means the key sets are the same.
  auto *items_it = mgp::MemHandlerCallback(map_iter_items, map1);
  bool equal = true;
  for (auto *item = mgp::map_items_iterator_get(items_it); item != nullptr && equal;
       item = mgp::map_items_iterator_next(items_it)) {
    auto *other_value = mgp::map_at(map2, mgp::map_item_key(item));
    equal = other_value != nullptr && util::ValuesEqual(mgp::map_item_value(item), other_value);
  }
  mgp::map_items_iterator_destroy(items_it);
  return equal;
}

/// @brief Returns whether two MGP API nodes are equal.
inline bool NodesEqual(mgp_vertex *node1, mgp_vertex *node2) {
  // With query modules, two nodes are identical if their IDs are equal
  if (node1 == node2) {
    return true;
  }
  if (mgp::vertex_get_id(node1).as_int != mgp::vertex_get_id(node2).as_int) {
    return false;
  }
  return true;
}

/// @brief Returns whether two MGP API relationships are equal.
inline bool RelationshipsEqual(mgp_edge *relationship1, mgp_edge *relationship2) {
  // With query modules, two relationships are identical if their IDs are equal
  if (relationship1 == relationship2) {
    return true;
  }
  if (mgp::edge_get_id(relationship1).as_int != mgp::edge_get_id(relationship2).as_int) {
    return false;
  }
  return true;
}

/// @brief Returns whether two MGP API paths are equal.
inline bool PathsEqual(mgp_path *path1, mgp_path *path2) {
  // With query modules, two paths are identical if their elements are pairwise identical
  if (path1 == path2) {
    return true;
  }
  if (mgp::path_size(path1) != mgp::path_size(path2)) {
    return false;
  }
  const auto path_size = mgp::path_size(path1);
  for (size_t i = 0; i < path_size; ++i) {
    if (!util::NodesEqual(mgp::path_vertex_at(path1, i), mgp::path_vertex_at(path2, i))) {
      return false;
    }
    if (!util::RelationshipsEqual(mgp::path_edge_at(path1, i), mgp::path_edge_at(path2, i))) {
      return false;
    }
  }
  return util::NodesEqual(mgp::path_vertex_at(path1, path_size), mgp::path_vertex_at(path2, path_size));
}

/// @brief Returns whether two MGP API date objects are equal.
inline bool DatesEqual(mgp_date *date1, mgp_date *date2) { return mgp::date_equal(date1, date2); }

/// @brief Returns whether two MGP API local time objects are equal.
inline bool LocalTimesEqual(mgp_local_time *local_time1, mgp_local_time *local_time2) {
  return mgp::local_time_equal(local_time1, local_time2);
}

/// @brief Returns whether two MGP API local datetime objects are equal.
inline bool LocalDateTimesEqual(mgp_local_date_time *local_date_time1, mgp_local_date_time *local_date_time2) {
  return mgp::local_date_time_equal(local_date_time1, local_date_time2);
}

/// @brief Returns whether two MGP API duration objects are equal.
inline bool DurationsEqual(mgp_duration *duration1, mgp_duration *duration2) {
  return mgp::duration_equal(duration1, duration2);
}

/// @brief Returns whether two MGP API zoned datetime objects are equal.
inline bool ZonedDateTimesEqual(mgp_zoned_date_time *zoned_date_time1, mgp_zoned_date_time *zoned_date_time2) {
  return mgp::zoned_date_time_equal(zoned_date_time1, zoned_date_time2);
}

/// @brief Returns whether two MGP API Point2d objects are equal.
inline bool Point2dsEqual(mgp_point_2d *p1, mgp_point_2d *p2) { return mgp::point_2d_equal(p1, p2); }

/// @brief Returns whether two MGP API Point3d objects are equal.
inline bool Point3dsEqual(mgp_point_3d *p1, mgp_point_3d *p2) { return mgp::point_3d_equal(p1, p2); }

/// @brief Returns whether two MGP API Enum objects are equal.
inline bool EnumsEqual(mgp_enum *e1, mgp_enum *e2) { return mgp::enum_equal(e1, e2); }

/// @brief Returns whether two MGP API values are equal.
inline bool ValuesEqual(mgp_value *value1, mgp_value *value2) {
  if (value1 == value2) {
    return true;
  }
  // Make int and double comparable, (ex. this is true -> 1.0 == 1)
  if (mgp::value_is_numeric(value1) && mgp::value_is_numeric(value2)) {
    return mgp::value_get_numeric(value1) == mgp::value_get_numeric(value2);
  }
  if (mgp::value_get_type(value1) != mgp::value_get_type(value2)) {
    return false;
  }
  switch (mgp::value_get_type(value1)) {
    case MGP_VALUE_TYPE_NULL:
      return true;
    case MGP_VALUE_TYPE_BOOL:
      return mgp::value_get_bool(value1) == mgp::value_get_bool(value2);
    case MGP_VALUE_TYPE_INT:
      return mgp::value_get_int(value1) == mgp::value_get_int(value2);
    case MGP_VALUE_TYPE_DOUBLE:
      return mgp::value_get_double(value1) == mgp::value_get_double(value2);
    case MGP_VALUE_TYPE_STRING:
      return std::string_view(mgp::value_get_string(value1)) == std::string_view(mgp::value_get_string(value2));
    case MGP_VALUE_TYPE_LIST:
      return util::ListsEqual(mgp::value_get_list(value1), mgp::value_get_list(value2));
    case MGP_VALUE_TYPE_MAP:
      return util::MapsEqual(mgp::value_get_map(value1), mgp::value_get_map(value2));
    case MGP_VALUE_TYPE_VERTEX:
      return util::NodesEqual(mgp::value_get_vertex(value1), mgp::value_get_vertex(value2));
    case MGP_VALUE_TYPE_EDGE:
      return util::RelationshipsEqual(mgp::value_get_edge(value1), mgp::value_get_edge(value2));
    case MGP_VALUE_TYPE_PATH:
      return util::PathsEqual(mgp::value_get_path(value1), mgp::value_get_path(value2));
    case MGP_VALUE_TYPE_DATE:
      return util::DatesEqual(mgp::value_get_date(value1), mgp::value_get_date(value2));
    case MGP_VALUE_TYPE_LOCAL_TIME:
      return util::LocalTimesEqual(mgp::value_get_local_time(value1), mgp::value_get_local_time(value2));
    case MGP_VALUE_TYPE_LOCAL_DATE_TIME:
      return util::LocalDateTimesEqual(mgp::value_get_local_date_time(value1), mgp::value_get_local_date_time(value2));
    case MGP_VALUE_TYPE_ZONED_DATE_TIME:
      return util::ZonedDateTimesEqual(mgp::value_get_zoned_date_time(value1), mgp::value_get_zoned_date_time(value2));
    case MGP_VALUE_TYPE_DURATION:
      return util::DurationsEqual(mgp::value_get_duration(value1), mgp::value_get_duration(value2));
    case MGP_VALUE_TYPE_POINT_2D:
      return util::Point2dsEqual(mgp::value_get_point_2d(value1), mgp::value_get_point_2d(value2));
    case MGP_VALUE_TYPE_POINT_3D:
      return util::Point3dsEqual(mgp::value_get_point_3d(value1), mgp::value_get_point_3d(value2));
    case MGP_VALUE_TYPE_ENUM:
      return util::EnumsEqual(mgp::value_get_enum(value1), mgp::value_get_enum(value2));
  }
  throw ValueException("Invalid value; does not match any Memgraph type.");
}

/// @brief Converts C++ API types to their MGP API equivalents.
inline mgp_type *ToMGPType(Type type) {
  switch (type) {
    case Type::Any:
      return mgp::type_any();
    case Type::Bool:
      return mgp::type_bool();
    case Type::Int:
      return mgp::type_int();
    case Type::Double:
      return mgp::type_float();
    case Type::String:
      return mgp::type_string();
    case Type::List:
      return mgp::type_list(mgp::type_any());
    case Type::Map:
      return mgp::type_map();
    case Type::Node:
      return mgp::type_node();
    case Type::Relationship:
      return mgp::type_relationship();
    case Type::Path:
      return mgp::type_path();
    case Type::Date:
      return mgp::type_date();
    case Type::LocalTime:
      return mgp::type_local_time();
    case Type::LocalDateTime:
      return mgp::type_local_date_time();
    case Type::Duration:
      return mgp::type_duration();
    case Type::ZonedDateTime:
      return mgp::type_zoned_date_time();
    case Type::Point2d:
      return mgp::type_point_2d();
    case Type::Point3d:
      return mgp::type_point_3d();
    case Type::Enum:
      return mgp::type_enum();
    default:
      break;
  }
  throw ValueException("Unknown type error!");
}

/// @brief Converts MGP API types to their C++ API equivalents.
inline Type ToAPIType(mgp_value_type type) {
  switch (type) {
    case MGP_VALUE_TYPE_NULL:
      return Type::Null;
    case MGP_VALUE_TYPE_BOOL:
      return Type::Bool;
    case MGP_VALUE_TYPE_INT:
      return Type::Int;
    case MGP_VALUE_TYPE_DOUBLE:
      return Type::Double;
    case MGP_VALUE_TYPE_STRING:
      return Type::String;
    case MGP_VALUE_TYPE_LIST:
      return Type::List;
    case MGP_VALUE_TYPE_MAP:
      return Type::Map;
    case MGP_VALUE_TYPE_VERTEX:
      return Type::Node;
    case MGP_VALUE_TYPE_EDGE:
      return Type::Relationship;
    case MGP_VALUE_TYPE_PATH:
      return Type::Path;
    case MGP_VALUE_TYPE_DATE:
      return Type::Date;
    case MGP_VALUE_TYPE_LOCAL_TIME:
      return Type::LocalTime;
    case MGP_VALUE_TYPE_LOCAL_DATE_TIME:
      return Type::LocalDateTime;
    case MGP_VALUE_TYPE_DURATION:
      return Type::Duration;
    case MGP_VALUE_TYPE_ZONED_DATE_TIME:
      return Type::ZonedDateTime;
    case MGP_VALUE_TYPE_POINT_2D:
      return Type::Point2d;
    case MGP_VALUE_TYPE_POINT_3D:
      return Type::Point3d;
    case MGP_VALUE_TYPE_ENUM:
      return Type::Enum;
  }
  throw ValueException("Unknown type error!");
}
}  // namespace util

/* #region Graph (Id, Graph, Nodes, GraphRelationships, Relationships, Properties & Labels) */

// Id:

inline Id Id::FromUint(uint64_t id) { return Id(util::MemcpyCast<int64_t>(id)); }

inline Id Id::FromInt(int64_t id) { return Id(id); }

inline int64_t Id::AsInt() const { return id_; }

inline uint64_t Id::AsUint() const { return util::MemcpyCast<uint64_t>(id_); }

inline bool Id::operator==(const Id &other) const { return id_ == other.id_; }

inline bool Id::operator!=(const Id &other) const { return !(*this == other); }

inline bool Id::operator<(const Id &other) const { return id_ < other.id_; }

inline Id::Id(int64_t id) : id_(id) {}

// Graph:

inline Graph::Graph(mgp_graph *graph) : graph_(graph) {}

inline AbortReason Graph::MustAbort() const {
  const auto reason = must_abort(graph_);
  switch (reason) {
    case 1:
      return AbortReason::TERMINATED;
    case 2:
      return AbortReason::SHUTDOWN;
    case 3:
      return AbortReason::TIMEOUT;
    case 4:
      return AbortReason::EXCEPTION;
    default:
      break;
  }
  return AbortReason::NO_ABORT;
}

inline void Graph::CheckMustAbort() const {
  switch (MustAbort()) {
    case AbortReason::TERMINATED:
      throw TerminatedMustAbortException();
    case AbortReason::SHUTDOWN:
      throw ShutdownMustAbortException();
    case AbortReason::TIMEOUT:
      throw TimeoutMustAbortException();
    case AbortReason::EXCEPTION:
      throw ExceptionMustAbortException();
    case AbortReason::NO_ABORT:
      break;
  }
}

inline int64_t Graph::Order() const {
  int64_t i = 0;
  for (const auto _ : Nodes()) {
    i++;
  }
  return i;
}

inline int64_t Graph::Size() const {
  int64_t i = 0;
  for (const auto _ : Relationships()) {
    i++;
  }
  return i;
}

inline GraphNodes Graph::Nodes() const {
  auto *nodes_it = mgp::MemHandlerCallback(graph_iter_vertices, graph_);
  if (nodes_it == nullptr) {
    throw mg_exception::NotEnoughMemoryException();
  }
  return GraphNodes(nodes_it);
}

inline GraphRelationships Graph::Relationships() const { return GraphRelationships(graph_); }

inline Node Graph::GetNodeById(const Id node_id) const {
  auto *mgp_node = mgp::MemHandlerCallback(graph_get_vertex_by_id, graph_, mgp_vertex_id{.as_int = node_id.AsInt()});
  if (mgp_node == nullptr) {
    mgp::vertex_destroy(mgp_node);
    throw NotFoundException("Node with ID " + std::to_string(node_id.AsUint()) + " not found!");
  }
  auto node = Node(mgp_node);
  mgp::vertex_destroy(mgp_node);
  return node;
}

inline bool Graph::ContainsNode(const Id node_id) const {
  auto *mgp_node = mgp::MemHandlerCallback(graph_get_vertex_by_id, graph_, mgp_vertex_id{.as_int = node_id.AsInt()});
  if (mgp_node == nullptr) {
    return false;
  }

  mgp::vertex_destroy(mgp_node);
  return true;
}

inline bool Graph::ContainsNode(const Node &node) const { return ContainsNode(node.Id()); }

inline bool Graph::ContainsRelationship(const Id relationship_id) const {
  for (const auto &graph_relationship : Relationships()) {
    if (graph_relationship.Id() == relationship_id) {
      return true;
    }
  }
  return false;
}

inline bool Graph::ContainsRelationship(const Relationship &relationship) const {
  for (const auto &graph_relationship : Relationships()) {
    if (relationship == graph_relationship) {
      return true;
    }
  }
  return false;
}

inline bool Graph::IsMutable() const { return mgp::graph_is_mutable(graph_); }

inline bool Graph::IsTransactional() const { return mgp::graph_is_transactional(graph_); }

inline Node Graph::CreateNode() {
  auto *vertex = mgp::MemHandlerCallback(graph_create_vertex, graph_);
  auto node = Node(vertex);

  mgp::vertex_destroy(vertex);

  return node;
}

inline void Graph::DeleteNode(const Node &node) { mgp::graph_delete_vertex(graph_, node.ptr_); }

inline void Graph::DetachDeleteNode(const Node &node) { mgp::graph_detach_delete_vertex(graph_, node.ptr_); };

inline Relationship Graph::CreateRelationship(const Node &from, const Node &to, const std::string_view type) {
  auto *edge =
      mgp::MemHandlerCallback(graph_create_edge, graph_, from.ptr_, to.ptr_, mgp_edge_type{.name = type.data()});
  auto relationship = Relationship(edge);

  mgp::edge_destroy(edge);

  return relationship;
}

inline void Graph::DeleteRelationship(const Relationship &relationship) {
  mgp::graph_delete_edge(graph_, relationship.ptr_);
}

// Nodes:

inline Nodes::Nodes(mgp_vertices_iterator *nodes_iterator)
    : nodes_iterator_{nodes_iterator, [](mgp_vertices_iterator *ptr) {
                        if (ptr != nullptr) {
                          mgp::vertices_iterator_destroy(ptr);
                        }
                      }} {}

inline Nodes::Iterator::Iterator(std::shared_ptr<mgp_vertices_iterator> nodes_iterator)
    : nodes_iterator_(std::move(nodes_iterator)) {  // COPY
  if (nodes_iterator_ == nullptr) {
    return;
  }

  if (mgp::vertices_iterator_get(nodes_iterator_.get()) == nullptr) {
    nodes_iterator_.reset();
  }
}

inline Nodes::Iterator::Iterator(const Iterator &other) : Iterator(other.nodes_iterator_) {}

inline Nodes::Iterator &Nodes::Iterator::operator++() {
  if (nodes_iterator_ != nullptr) {
    auto *next = mgp::vertices_iterator_next(nodes_iterator_.get());

    if (next == nullptr) {
      nodes_iterator_.reset();
      return *this;
    }
    index_++;
  }
  return *this;
}

inline Nodes::Iterator Nodes::Iterator::operator++(int) {
  auto retval = *this;
  ++*this;
  return retval;
}

inline bool Nodes::Iterator::operator==(Iterator other) const {
  if (nodes_iterator_ == nullptr && other.nodes_iterator_ == nullptr) {
    return true;
  }
  if (nodes_iterator_ == nullptr || other.nodes_iterator_ == nullptr) {
    return false;
  }
  return mgp::vertex_equal(mgp::vertices_iterator_get(nodes_iterator_.get()),
                           mgp::vertices_iterator_get(other.nodes_iterator_.get())) &&
         index_ == other.index_;
}

inline bool Nodes::Iterator::operator!=(Iterator other) const { return !(*this == other); }

inline Node Nodes::Iterator::operator*() const {
  if (nodes_iterator_ == nullptr) {
    return Node((const mgp_vertex *)nullptr);
  }

  return Node(mgp::vertices_iterator_get(nodes_iterator_.get()));
}

inline Nodes::Iterator Nodes::begin() const { return Iterator(nodes_iterator_); }

inline Nodes::Iterator Nodes::end() const { return Iterator(nullptr); }

inline Nodes::Iterator Nodes::cbegin() const { return Iterator(nodes_iterator_); }

inline Nodes::Iterator Nodes::cend() const { return Iterator(nullptr); }

// GraphRelationships:

inline GraphRelationships::GraphRelationships(mgp_graph *graph) : graph_(graph) {}

inline GraphRelationships::Iterator::Iterator(mgp_vertices_iterator *nodes_iterator) : nodes_iterator_(nodes_iterator) {
  // Positions the iterator over the first existing relationship

  if (nodes_iterator_ == nullptr) {
    return;
  }

  // Go through each graph node’s adjacent nodes
  for (auto *node = mgp::vertices_iterator_get(nodes_iterator_); node;
       node = mgp::vertices_iterator_next(nodes_iterator_)) {
    // Check if node exists
    if (node == nullptr) {
      mgp::vertices_iterator_destroy(nodes_iterator_);
      nodes_iterator_ = nullptr;
      return;
    }

    // Check if node has out-relationships
    out_relationships_iterator_ = mgp::MemHandlerCallback(vertex_iter_out_edges, node);
    auto *relationship = mgp::edges_iterator_get(out_relationships_iterator_);
    if (relationship != nullptr) {
      return;
    }

    mgp::edges_iterator_destroy(out_relationships_iterator_);
    out_relationships_iterator_ = nullptr;
  }
}

inline GraphRelationships::Iterator::Iterator(const Iterator &other) : Iterator(other.nodes_iterator_) {}

inline GraphRelationships::Iterator::~Iterator() {
  if (nodes_iterator_ != nullptr) {
    mgp::vertices_iterator_destroy(nodes_iterator_);
  }
  if (out_relationships_iterator_ != nullptr) {
    mgp::edges_iterator_destroy(out_relationships_iterator_);
  }
}

inline GraphRelationships::Iterator &GraphRelationships::Iterator::operator++() {
  // Moves the iterator onto the next existing relationship

  // 1. Check if the current node has remaining relationships to iterate over

  if (out_relationships_iterator_ != nullptr) {
    auto *next = mgp::edges_iterator_next(out_relationships_iterator_);

    if (next != nullptr) {
      return *this;
    }

    mgp::edges_iterator_destroy(out_relationships_iterator_);
    out_relationships_iterator_ = nullptr;
  }

  // 2. Move onto the next nodes

  if (nodes_iterator_ != nullptr) {
    for (auto *node = mgp::vertices_iterator_next(nodes_iterator_); node;
         node = mgp::vertices_iterator_next(nodes_iterator_)) {
      // Check if node exists - if it doesn’t, we’ve reached the end of the iterator
      if (node == nullptr) {
        mgp::vertices_iterator_destroy(nodes_iterator_);
        nodes_iterator_ = nullptr;
        return *this;
      }

      // Check if node has out-relationships
      out_relationships_iterator_ = mgp::MemHandlerCallback(vertex_iter_out_edges, node);
      auto *relationship = mgp::edges_iterator_get(out_relationships_iterator_);
      if (relationship != nullptr) {
        return *this;
      }

      mgp::edges_iterator_destroy(out_relationships_iterator_);
      out_relationships_iterator_ = nullptr;
    }
  }
  mgp::vertices_iterator_destroy(nodes_iterator_);
  nodes_iterator_ = nullptr;
  return *this;
}

inline GraphRelationships::Iterator GraphRelationships::Iterator::operator++(int) {
  auto retval = *this;
  ++*this;
  return retval;
}

inline bool GraphRelationships::Iterator::operator==(Iterator other) const {
  if (out_relationships_iterator_ == nullptr && other.out_relationships_iterator_ == nullptr) {
    return true;
  }
  if (out_relationships_iterator_ == nullptr || other.out_relationships_iterator_ == nullptr) {
    return false;
  }
  return mgp::edge_equal(mgp::edges_iterator_get(out_relationships_iterator_),
                         mgp::edges_iterator_get(other.out_relationships_iterator_)) &&
         index_ == other.index_;
}

inline bool GraphRelationships::Iterator::operator!=(Iterator other) const { return !(*this == other); }

inline Relationship GraphRelationships::Iterator::operator*() const {
  if (out_relationships_iterator_ != nullptr) {
    return Relationship(mgp::edges_iterator_get(out_relationships_iterator_));
  }

  return Relationship((mgp_edge *)nullptr);
}

inline GraphRelationships::Iterator GraphRelationships::begin() const {
  return Iterator(mgp::MemHandlerCallback(graph_iter_vertices, graph_));
}

inline GraphRelationships::Iterator GraphRelationships::end() const { return Iterator(nullptr); }

inline GraphRelationships::Iterator GraphRelationships::cbegin() const {
  return Iterator(mgp::MemHandlerCallback(graph_iter_vertices, graph_));
}

inline GraphRelationships::Iterator GraphRelationships::cend() const { return Iterator(nullptr); }

// Relationships:

inline Relationships::Relationships(mgp_edges_iterator *relationships_iterator)
    : relationships_iterator_(relationships_iterator, [](mgp_edges_iterator *ptr) {
        if (ptr != nullptr) {
          mgp::edges_iterator_destroy(ptr);
        }
      }) {}

inline Relationships::Iterator::Iterator(std::shared_ptr<mgp_edges_iterator> relationships_iterator)
    : relationships_iterator_(std::move(relationships_iterator)) {
  if (relationships_iterator_ == nullptr) {
    return;
  }
  if (mgp::edges_iterator_get(relationships_iterator_.get()) == nullptr) {
    relationships_iterator_.reset();
  }
}

inline Relationships::Iterator::Iterator(const Iterator &other) : Iterator(other.relationships_iterator_) {}

inline Relationships::Iterator &Relationships::Iterator::operator++() {
  if (relationships_iterator_ != nullptr) {
    auto *next = mgp::edges_iterator_next(relationships_iterator_.get());

    if (next == nullptr) {
      relationships_iterator_.reset();
      return *this;
    }
    index_++;
  }
  return *this;
}

inline Relationships::Iterator Relationships::Iterator::operator++(int) {
  auto retval = *this;
  ++*this;
  return retval;
}

inline bool Relationships::Iterator::operator==(Iterator other) const {
  if (relationships_iterator_ == nullptr && other.relationships_iterator_ == nullptr) {
    return true;
  }
  if (relationships_iterator_ == nullptr || other.relationships_iterator_ == nullptr) {
    return false;
  }
  return mgp::edge_equal(mgp::edges_iterator_get(relationships_iterator_.get()),
                         mgp::edges_iterator_get(other.relationships_iterator_.get())) &&
         index_ == other.index_;
}

inline bool Relationships::Iterator::operator!=(Iterator other) const { return !(*this == other); }

inline Relationship Relationships::Iterator::operator*() const {
  if (relationships_iterator_ == nullptr) {
    return Relationship((mgp_edge *)nullptr);
  }

  return Relationship(mgp::edges_iterator_get(relationships_iterator_.get()));
}

inline Relationships::Iterator Relationships::begin() const { return Iterator(relationships_iterator_); }

inline Relationships::Iterator Relationships::end() const { return Iterator(nullptr); }

inline Relationships::Iterator Relationships::cbegin() const { return Iterator(relationships_iterator_); }

inline Relationships::Iterator Relationships::cend() const { return Iterator(nullptr); }

// Labels:

inline Labels::Labels(mgp_vertex *node_ptr) : node_ptr_(mgp::MemHandlerCallback(vertex_copy, node_ptr)) {}

inline Labels::Labels(const Labels &other) : Labels(other.node_ptr_) {}

inline Labels::Labels(Labels &&other) noexcept : node_ptr_(other.node_ptr_) { other.node_ptr_ = nullptr; }

inline Labels &Labels::operator=(const Labels &other) {
  if (this != &other) {
    mgp::vertex_destroy(node_ptr_);

    node_ptr_ = mgp::MemHandlerCallback(vertex_copy, other.node_ptr_);
  }
  return *this;
}

inline Labels &Labels::operator=(Labels &&other) noexcept {
  if (this != &other) {
    mgp::vertex_destroy(node_ptr_);

    node_ptr_ = other.node_ptr_;
    other.node_ptr_ = nullptr;
  }
  return *this;
}

inline Labels::~Labels() {
  if (node_ptr_ != nullptr) {
    mgp::vertex_destroy(node_ptr_);
  }
}

inline bool Labels::Iterator::operator==(const Iterator &other) const {
  return iterable_ == other.iterable_ && index_ == other.index_;
}

inline bool Labels::Iterator::operator!=(const Iterator &other) const { return !(*this == other); }

inline Labels::Iterator &Labels::Iterator::operator++() {
  index_++;
  return *this;
}

inline std::string_view Labels::Iterator::operator*() const { return (*iterable_)[index_]; }

inline Labels::Iterator::Iterator(const Labels *iterable, size_t index) : iterable_(iterable), index_(index) {}

inline size_t Labels::Size() const { return mgp::vertex_labels_count(node_ptr_); }

inline std::string_view Labels::operator[](size_t index) const { return mgp::vertex_label_at(node_ptr_, index).name; }

inline Labels::Iterator Labels::begin() { return Iterator(this, 0); }

inline Labels::Iterator Labels::end() { return Iterator(this, Size()); }

inline Labels::Iterator Labels::cbegin() { return Iterator(this, 0); }

inline Labels::Iterator Labels::cend() { return Iterator(this, Size()); }

/* #endregion */

/* #region Types */

/* #region Containers (List, Map) */

// List:

inline List::List(mgp_list *ptr) : ptr_(mgp::MemHandlerCallback(list_copy, ptr)) {}

inline List::List(mgp_list *ptr, StealType) : ptr_(ptr) {}

inline List::List(const mgp_list *const_ptr)
    : ptr_(mgp::MemHandlerCallback(list_copy, const_cast<mgp_list *>(const_ptr))) {}

inline List::List() : ptr_(mgp::MemHandlerCallback(list_make_empty, 0)) {}

inline List::List(size_t capacity) : ptr_(mgp::MemHandlerCallback(list_make_empty, capacity)) {}

inline List::List(const std::vector<Value> &values) : ptr_(mgp::MemHandlerCallback(list_make_empty, values.size())) {
  for (const auto &value : values) {
    AppendExtend(value);
  }
}

inline List::List(std::vector<Value> &&values) : ptr_(mgp::MemHandlerCallback(list_make_empty, values.size())) {
  for (auto &value : values) {
    Append(std::move(value));
  }
}

inline List::List(const std::initializer_list<Value> values)
    : ptr_(mgp::MemHandlerCallback(list_make_empty, values.size())) {
  for (const auto &value : values) {
    AppendExtend(value);
  }
}

inline List::List(const List &other) : List(other.ptr_) {}

inline List::List(List &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }

inline List &List::operator=(const List &other) {
  if (this != &other) {
    mgp::list_destroy(ptr_);

    ptr_ = mgp::MemHandlerCallback(list_copy, other.ptr_);
  }
  return *this;
}

inline List &List::operator=(List &&other) noexcept {
  if (this != &other) {
    mgp::list_destroy(ptr_);

    ptr_ = other.ptr_;
    other.ptr_ = nullptr;
  }
  return *this;
}

inline List::~List() {
  if (ptr_ != nullptr) {
    mgp::list_destroy(ptr_);
  }
}

inline bool List::ContainsDeleted() const { return mgp::list_contains_deleted(ptr_); }

inline size_t List::Size() const { return mgp::list_size(ptr_); }

inline bool List::Empty() const { return Size() == 0; }

inline Value List::operator[](size_t index) const { return Value(ref_type, mgp::list_at(ptr_, index)); }

inline Value List::operator[](size_t index) { return Value(ref_type, mgp::list_at(ptr_, index)); }

inline bool List::Iterator::operator==(const Iterator &other) const {
  return iterable_ == other.iterable_ && index_ == other.index_;
}

inline bool List::Iterator::operator!=(const Iterator &other) const { return !(*this == other); }

inline List::Iterator &List::Iterator::operator++() {
  index_++;
  return *this;
}

inline Value List::Iterator::operator*() const { return (*iterable_)[index_]; }

inline List::Iterator::Iterator(const List *iterable, size_t index) : iterable_(iterable), index_(index) {}

inline List::Iterator List::begin() const { return Iterator(this, 0); }

inline List::Iterator List::end() const { return Iterator(this, Size()); }

inline List::Iterator List::cbegin() const { return Iterator(this, 0); }

inline List::Iterator List::cend() const { return Iterator(this, Size()); }

inline void List::Append(const Value &value) { mgp::list_append(ptr_, value.ptr()); }

inline void List::AppendExtend(const Value &value) { mgp::list_append_extend(ptr_, value.ptr()); }

inline void List::Reserve(size_t n) { mgp::list_reserve(ptr_, n); }

inline bool List::operator==(const List &other) const { return util::ListsEqual(ptr_, other.ptr_); }

inline bool List::operator!=(const List &other) const { return !(*this == other); }

inline std::string List::ToString() const {
  const size_t size = Size();
  if (size == 0) {
    return "[]";
  }
  std::string return_str{"["};
  size_t i = 0;
  const mgp::List &list = (*this);
  while (i < size - 1) {
    return_str.append(list[i].ToString() + ", ");
    i++;
  }
  return_str.append(list[i].ToString() + "]");
  return return_str;
}

inline mgp_list *List::GetPtr() const { return ptr_; }

// MapItem:

inline bool MapItem::operator==(MapItem &other) const { return key == other.key && value == other.value; }

inline bool MapItem::operator!=(MapItem &other) const { return !(*this == other); }

inline bool MapItem::operator<(const MapItem &other) const { return key < other.key; }

// Map:

inline Map::Map(mgp_map *ptr) : ptr_(mgp::MemHandlerCallback(map_copy, ptr)) {}

inline Map::Map(mgp_map *ptr, StealType) : ptr_(ptr) {}

inline Map::Map(const mgp_map *const_ptr) : ptr_(mgp::MemHandlerCallback(map_copy, const_cast<mgp_map *>(const_ptr))) {}

inline Map::Map() : ptr_(mgp::MemHandlerCallback(map_make_empty)) {}

inline Map::Map(const std::map<std::string_view, Value> &items) : ptr_(mgp::MemHandlerCallback(map_make_empty)) {
  for (const auto &[key, value] : items) {
    Insert(key, value);
  }
}

inline Map::Map(std::map<std::string_view, Value> &&items) : ptr_(mgp::MemHandlerCallback(map_make_empty)) {
  for (auto &[key, value] : items) {
    Insert(key, value);
  }
}

inline Map::Map(const std::initializer_list<std::pair<std::string_view, Value>> items)
    : ptr_(mgp::MemHandlerCallback(map_make_empty)) {
  for (const auto &[key, value] : items) {
    Insert(key, value);
  }
}

inline Map::Map(const Map &other) : Map(other.ptr_) {}

inline Map::Map(Map &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }

inline Map &Map::operator=(const Map &other) {
  if (this != &other) {
    mgp::map_destroy(ptr_);

    ptr_ = mgp::MemHandlerCallback(map_copy, other.ptr_);
  }
  return *this;
}

inline Map &Map::operator=(Map &&other) noexcept {
  if (this != &other) {
    mgp::map_destroy(ptr_);

    ptr_ = other.ptr_;
    other.ptr_ = nullptr;
  }
  return *this;
}

inline Map::~Map() {
  if (ptr_ != nullptr) {
    mgp::map_destroy(ptr_);
  }
}

inline bool Map::ContainsDeleted() const { return mgp::map_contains_deleted(ptr_); }

inline size_t Map::Size() const { return mgp::map_size(ptr_); }

inline bool Map::Empty() const { return Size() == 0; }

inline Value Map::operator[](std::string_view key) const { return Value(ref_type, mgp::map_at(ptr_, key.data())); }

inline Value Map::operator[](std::string_view key) { return Value(ref_type, mgp::map_at(ptr_, key.data())); }

inline Value Map::At(std::string_view key) const {
  auto *ptr = mgp::map_at(ptr_, key.data());
  if (ptr) {
    return Value(ref_type, ptr);
  }
  return Value();
}

inline bool Map::KeyExists(std::string_view key) const { return mgp::key_exists(ptr_, key.data()); }

inline Map::Iterator::Iterator(mgp_map_items_iterator *map_items_iterator) : map_items_iterator_(map_items_iterator) {
  if (map_items_iterator_ == nullptr) return;
  if (mgp::map_items_iterator_get(map_items_iterator_) == nullptr) {
    mgp::map_items_iterator_destroy(map_items_iterator_);
    map_items_iterator_ = nullptr;
  }
}

inline Map::Iterator::Iterator(const Iterator &other) : Iterator(other.map_items_iterator_) {}

inline Map::Iterator::~Iterator() {
  if (map_items_iterator_ != nullptr) {
    mgp::map_items_iterator_destroy(map_items_iterator_);
  }
}

inline Map::Iterator &Map::Iterator::operator++() {
  if (map_items_iterator_ != nullptr) {
    auto *next = mgp::map_items_iterator_next(map_items_iterator_);

    if (next == nullptr) {
      mgp::map_items_iterator_destroy(map_items_iterator_);
      map_items_iterator_ = nullptr;
      return *this;
    }
  }
  return *this;
}

inline Map::Iterator Map::Iterator::operator++(int) {
  auto retval = *this;
  ++*this;
  return retval;
}

inline bool Map::Iterator::operator==(Iterator other) const {
  if (map_items_iterator_ == nullptr && other.map_items_iterator_ == nullptr) {
    return true;
  }
  if (map_items_iterator_ == nullptr || other.map_items_iterator_ == nullptr) {
    return false;
  }
  return mgp::map_items_iterator_get(map_items_iterator_) == mgp::map_items_iterator_get(other.map_items_iterator_);
}

inline bool Map::Iterator::operator!=(Iterator other) const { return !(*this == other); }

inline MapItem Map::Iterator::operator*() const {
  if (map_items_iterator_ == nullptr) {
    throw ValueException("Empty map item!");
  }

  auto *raw_map_item = mgp::map_items_iterator_get(map_items_iterator_);

  const auto *map_key = mgp::map_item_key(raw_map_item);
  auto map_value = Value(mgp::map_item_value(raw_map_item));

  return MapItem{.key = map_key, .value = map_value};
}

inline Map::Iterator Map::begin() const { return Iterator(mgp::MemHandlerCallback(map_iter_items, ptr_)); }

inline Map::Iterator Map::end() const { return Iterator(nullptr); }

inline Map::Iterator Map::cbegin() const { return Iterator(mgp::MemHandlerCallback(map_iter_items, ptr_)); }

inline Map::Iterator Map::cend() const { return Iterator(nullptr); }

inline void Map::Insert(std::string_view key, const Value &value) { mgp::map_insert(ptr_, key.data(), value.ptr()); }

inline void Map::Update(std::string_view key, const Value &value) { mgp::map_update(ptr_, key.data(), value.ptr()); }

inline void Map::Erase(std::string_view key) { mgp::map_erase(ptr_, key.data()); }

inline bool Map::operator==(const Map &other) const { return util::MapsEqual(ptr_, other.ptr_); }

inline bool Map::operator!=(const Map &other) const { return !(*this == other); }

inline std::string Map::ToString() const {
  const size_t map_size = Size();
  if (map_size == 0) {
    return "{}";
  }
  std::string return_string{"{"};
  size_t i = 0;
  for (const auto &[key, value] : *this) {
    if (i == map_size - 1) {
      return_string.append(std::string(key) + ": " + value.ToString() + "}");
      break;
    }
    return_string.append(std::string(key) + ": " + value.ToString() + ", ");
    ++i;
  }
  return return_string;
}

/* #endregion */

/* #region Graph elements (Node, Relationship & Path) */

// Node:

inline Node::Node(mgp_vertex *ptr) : ptr_(MemHandlerCallback(vertex_copy, ptr)) {}

inline Node::Node(const mgp_vertex *const_ptr)
    : ptr_(mgp::MemHandlerCallback(vertex_copy, const_cast<mgp_vertex *>(const_ptr))) {}

inline Node::Node(const Node &other) : Node(other.ptr_) {}

inline Node::Node(Node &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }

inline Node &Node::operator=(const Node &other) {
  if (this != &other) {
    mgp::vertex_destroy(ptr_);

    ptr_ = mgp::MemHandlerCallback(vertex_copy, other.ptr_);
  }
  return *this;
}

inline Node &Node::operator=(Node &&other) noexcept {
  if (this != &other) {
    mgp::vertex_destroy(ptr_);

    ptr_ = other.ptr_;
    other.ptr_ = nullptr;
  }
  return *this;
}

inline Node::~Node() {
  if (ptr_ != nullptr) {
    mgp::vertex_destroy(ptr_);
  }
}

inline bool Node::IsDeleted() const { return mgp::vertex_is_deleted(ptr_); }

inline mgp_vertex *Node::GetPtr() const { return ptr_; }

inline mgp::Id Node::Id() const { return Id::FromInt(mgp::vertex_get_id(ptr_).as_int); }

inline mgp::Labels Node::Labels() const { return mgp::Labels(ptr_); }

inline bool Node::HasLabel(std::string_view label) const {
  for (const auto node_label : Labels()) {
    if (label == node_label) {
      return true;
    }
  }
  return false;
}

inline Relationships Node::InRelationships() const {
  auto *relationship_iterator = mgp::MemHandlerCallback(vertex_iter_in_edges, ptr_);
  if (relationship_iterator == nullptr) {
    throw mg_exception::NotEnoughMemoryException();
  }
  return Relationships(relationship_iterator);
}

inline Relationships Node::OutRelationships() const {
  auto *relationship_iterator = mgp::MemHandlerCallback(vertex_iter_out_edges, ptr_);
  if (relationship_iterator == nullptr) {
    throw mg_exception::NotEnoughMemoryException();
  }
  return Relationships(relationship_iterator);
}

inline void Node::AddLabel(const std::string_view label) {
  mgp::vertex_add_label(this->ptr_, mgp_label{.name = label.data()});
}

inline void Node::RemoveLabel(const std::string_view label) {
  mgp::vertex_remove_label(this->ptr_, mgp_label{.name = label.data()});
}

inline std::unordered_map<std::string, Value> Node::Properties() const {
  mgp_properties_iterator *properties_iterator = mgp::MemHandlerCallback(vertex_iter_properties, ptr_);
  std::unordered_map<std::string, Value> property_map;
  for (auto *property = mgp::properties_iterator_get(properties_iterator); property;
       property = mgp::properties_iterator_next(properties_iterator)) {
    property_map.emplace(std::string(property->name), Value(property->value));
  }
  mgp::properties_iterator_destroy(properties_iterator);
  return property_map;
}

inline void Node::SetProperty(std::string property, Value value) {
  mgp::vertex_set_property(ptr_, property.data(), value.ptr());
}

inline void Node::SetProperties(std::unordered_map<std::string_view, Value> properties) {
  mgp_map *map = mgp::MemHandlerCallback(map_make_empty);

  for (auto const &[k, v] : properties) {
    mgp::map_insert(map, k.data(), v.ptr());
  }

  mgp::vertex_set_properties(ptr_, map);
  mgp::map_destroy(map);
}

inline void Node::RemoveProperty(std::string property) { SetProperty(property, Value()); }

inline Value Node::GetProperty(const std::string &property) const {
  mgp_value *vertex_prop = mgp::MemHandlerCallback(vertex_get_property, ptr_, property.data());
  return Value(steal_type, vertex_prop);
}

inline bool Node::operator<(const Node &other) const { return Id() < other.Id(); }

inline bool Node::operator==(const Node &other) const { return util::NodesEqual(ptr_, other.ptr_); }

inline bool Node::operator!=(const Node &other) const { return !(*this == other); }

// this functions is used both in relationship and node ToString
inline std::string PropertiesToString(const std::map<std::string, Value> &property_map) {
  std::string properties;
  const auto map_size = property_map.size();
  size_t i = 0;
  for (const auto &[key, value] : property_map) {
    if (i == map_size - 1) {
      properties.append(std::string(key) + ": " + value.ToString());
      break;
    }
    properties.append(std::string(key) + ": " + value.ToString() + ", ");
    ++i;
  }
  return properties;
}

inline std::string Node::ToString() const {
  std::string labels{", "};
  for (auto label : Labels()) {
    labels.append(":" + std::string(label));
  }
  if (labels == ", ") {
    labels = "";  // dont use labels if they dont exist
  }
  std::unordered_map<std::string, Value> properties_map{Properties()};
  std::map<std::string, Value> properties_map_sorted{};

  for (const auto &[k, v] : properties_map) {
    properties_map_sorted.emplace(k, v);
  }
  std::string properties{PropertiesToString(properties_map_sorted)};

  return "(id: " + std::to_string(Id().AsInt()) + labels + ", properties: {" + properties + "})";
}

inline size_t Node::InDegree() const { return mgp::vertex_get_in_degree(ptr_); }

inline size_t Node::OutDegree() const { return mgp::vertex_get_out_degree(ptr_); }

// Relationship:

inline Relationship::Relationship(mgp_edge *ptr) : ptr_(mgp::MemHandlerCallback(edge_copy, ptr)) {}

inline Relationship::Relationship(const mgp_edge *const_ptr)
    : ptr_(mgp::MemHandlerCallback(edge_copy, const_cast<mgp_edge *>(const_ptr))) {}

inline Relationship::Relationship(const Relationship &other) : Relationship(other.ptr_) {}

inline Relationship::Relationship(Relationship &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }

inline Relationship &Relationship::operator=(const Relationship &other) {
  if (this != &other) {
    mgp::edge_destroy(ptr_);

    ptr_ = mgp::MemHandlerCallback(edge_copy, other.ptr_);
  }
  return *this;
}

inline Relationship &Relationship::operator=(Relationship &&other) noexcept {
  if (this != &other) {
    mgp::edge_destroy(ptr_);

    ptr_ = other.ptr_;
    other.ptr_ = nullptr;
  }
  return *this;
}

inline Relationship::~Relationship() {
  if (ptr_ != nullptr) {
    mgp::edge_destroy(ptr_);
  }
}

inline bool Relationship::IsDeleted() const { return mgp::edge_is_deleted(ptr_); }

inline mgp::Id Relationship::Id() const { return Id::FromInt(mgp::edge_get_id(ptr_).as_int); }

inline std::string_view Relationship::Type() const { return mgp::edge_get_type(ptr_).name; }

inline std::unordered_map<std::string, Value> Relationship::Properties() const {
  mgp_properties_iterator *properties_iterator = mgp::MemHandlerCallback(edge_iter_properties, ptr_);
  std::unordered_map<std::string, Value> property_map;
  for (mgp_property *property = mgp::properties_iterator_get(properties_iterator); property;
       property = mgp::properties_iterator_next(properties_iterator)) {
    property_map.emplace(property->name, Value(property->value));
  }
  mgp::properties_iterator_destroy(properties_iterator);
  return property_map;
}

inline void Relationship::SetProperty(std::string property, Value value) {
  mgp::edge_set_property(ptr_, property.data(), value.ptr());
}

inline void Relationship::SetProperties(std::unordered_map<std::string_view, Value> properties) {
  mgp_map *map = mgp::MemHandlerCallback(map_make_empty);

  for (auto const &[k, v] : properties) {
    mgp::map_insert(map, k.data(), v.ptr());
  }

  mgp::edge_set_properties(ptr_, map);
  mgp::map_destroy(map);
}

inline void Relationship::RemoveProperty(std::string property) { SetProperty(property, Value()); }

inline Value Relationship::GetProperty(const std::string &property) const {
  mgp_value *edge_prop = mgp::MemHandlerCallback(edge_get_property, ptr_, property.data());
  return Value(steal_type, edge_prop);
}

inline Node Relationship::From() const { return Node(mgp::edge_get_from(ptr_)); }

inline Node Relationship::To() const { return Node(mgp::edge_get_to(ptr_)); }

inline bool Relationship::operator<(const Relationship &other) const { return Id() < other.Id(); }

inline bool Relationship::operator==(const Relationship &other) const {
  return util::RelationshipsEqual(ptr_, other.ptr_);
}

inline bool Relationship::operator!=(const Relationship &other) const { return !(*this == other); }

inline std::string Relationship::ToString() const {
  const auto from = From();
  const auto to = To();

  const std::string type{Type()};
  std::unordered_map<std::string, Value> properties_map{Properties()};
  std::map<std::string, Value> properties_map_sorted{};

  for (const auto &[k, v] : properties_map) {
    properties_map_sorted.emplace(k, v);
  }
  std::string properties{PropertiesToString(properties_map_sorted)};

  const std::string relationship{"[type: " + type + ", id: " + std::to_string(Id().AsInt()) + ", properties: {" +
                                 properties + "}]"};

  return from.ToString() + "-" + relationship + "->" + to.ToString();
}

// Path:

inline Path::Path(mgp_path *ptr) : ptr_(mgp::MemHandlerCallback(path_copy, ptr)) {}

inline Path::Path(const mgp_path *const_ptr)
    : ptr_(mgp::MemHandlerCallback(path_copy, const_cast<mgp_path *>(const_ptr))) {}

inline Path::Path(const Node &start_node) : ptr_(mgp::MemHandlerCallback(path_make_with_start, start_node.ptr_)) {}

inline Path::Path(const Path &other) : Path(other.ptr_) {}

inline Path::Path(Path &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }

inline Path &Path::operator=(const Path &other) {
  if (this != &other) {
    mgp::path_destroy(ptr_);

    ptr_ = mgp::MemHandlerCallback(path_copy, other.ptr_);
  }
  return *this;
}

inline Path &Path::operator=(Path &&other) noexcept {
  if (this != &other) {
    mgp::path_destroy(ptr_);

    ptr_ = other.ptr_;
    other.ptr_ = nullptr;
  }
  return *this;
}

inline Path::~Path() {
  if (ptr_ != nullptr) {
    mgp::path_destroy(ptr_);
  }
}

inline bool Path::ContainsDeleted() const { return mgp::path_contains_deleted(ptr_); }

inline size_t Path::Length() const { return mgp::path_size(ptr_); }

inline Node Path::GetNodeAt(size_t index) const {
  auto *node_ptr = mgp::path_vertex_at(ptr_, index);
  if (node_ptr == nullptr) {
    throw IndexException("Index value out of bounds.");
  }
  return Node(node_ptr);
}

inline Relationship Path::GetRelationshipAt(size_t index) const {
  auto *relationship_ptr = mgp::path_edge_at(ptr_, index);
  if (relationship_ptr == nullptr) {
    throw IndexException("Index value out of bounds.");
  }
  return Relationship(relationship_ptr);
}

inline void Path::Expand(const Relationship &relationship) { mgp::path_expand(ptr_, relationship.ptr_); }

inline void Path::Pop() { mgp::path_pop(ptr_); }

inline bool Path::operator==(const Path &other) const { return util::PathsEqual(ptr_, other.ptr_); }

inline bool Path::operator!=(const Path &other) const { return !(*this == other); }

inline std::string Path::ToString() const {
  const auto length = Length();
  size_t i = 0;
  std::string return_string;
  for (i = 0; i < length; i++) {
    const auto node = GetNodeAt(i);
    return_string.append(node.ToString() + "-");

    const Relationship rel = GetRelationshipAt(i);
    std::unordered_map<std::string, Value> properties_map{rel.Properties()};
    std::map<std::string, Value> properties_map_sorted{};

    for (const auto &[k, v] : properties_map) {
      properties_map_sorted.emplace(k, v);
    }
    std::string properties{PropertiesToString(properties_map_sorted)};

    return_string.append("[type: " + std::string(rel.Type()) + ", id: " + std::to_string(rel.Id().AsInt()) +
                         ", properties: {" + properties + "}]->");
  }

  const auto node = GetNodeAt(i);
  return_string.append(node.ToString());
  return return_string;
}

/* #endregion */

/* #region Temporal types (Date, LocalTime, LocalDateTime, Duration) */

// Date:

inline Date::Date(mgp_date *ptr) : ptr_(mgp::MemHandlerCallback(date_copy, ptr)) {}

inline Date::Date(const mgp_date *const_ptr)
    : ptr_(mgp::MemHandlerCallback(date_copy, const_cast<mgp_date *>(const_ptr))) {}

inline Date::Date(std::string_view string) : ptr_(mgp::MemHandlerCallback(date_from_string, string.data())) {}

inline Date::Date(int year, int month, int day) {
  mgp_date_parameters params{.year = year, .month = month, .day = day};
  ptr_ = mgp::MemHandlerCallback(date_from_parameters, &params);
}

inline Date::Date(const Date &other) : Date(other.ptr_) {}

inline Date::Date(Date &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }

inline Date &Date::operator=(const Date &other) {
  if (this != &other) {
    mgp::date_destroy(ptr_);

    ptr_ = mgp::MemHandlerCallback(date_copy, other.ptr_);
  }
  return *this;
}

inline Date &Date::operator=(Date &&other) noexcept {
  if (this != &other) {
    mgp::date_destroy(ptr_);

    ptr_ = other.ptr_;
    other.ptr_ = nullptr;
  }
  return *this;
}

inline Date::~Date() {
  if (ptr_ != nullptr) {
    mgp::date_destroy(ptr_);
  }
}

inline Date Date::Now() {
  auto *mgp_date = mgp::MemHandlerCallback(date_now);
  auto date = Date(mgp_date);
  mgp::date_destroy(mgp_date);

  return date;
}

inline int Date::Year() const { return mgp::date_get_year(ptr_); }

inline int Date::Month() const { return mgp::date_get_month(ptr_); }

inline int Date::Day() const { return mgp::date_get_day(ptr_); }

inline int64_t Date::Timestamp() const { return mgp::date_timestamp(ptr_); }

inline bool Date::operator==(const Date &other) const { return util::DatesEqual(ptr_, other.ptr_); }

inline Date Date::operator+(const Duration &dur) const {
  auto *mgp_sum = mgp::MemHandlerCallback(date_add_duration, ptr_, dur.ptr_);
  auto sum = Date(mgp_sum);
  mgp::date_destroy(mgp_sum);

  return sum;
}

inline Date Date::operator-(const Duration &dur) const {
  auto *mgp_difference = mgp::MemHandlerCallback(date_add_duration, ptr_, dur.ptr_);
  auto difference = Date(mgp_difference);
  mgp::date_destroy(mgp_difference);

  return difference;
}

inline Duration Date::operator-(const Date &other) const {
  auto *mgp_difference = mgp::MemHandlerCallback(date_diff, ptr_, other.ptr_);
  auto difference = Duration(mgp_difference);
  mgp::duration_destroy(mgp_difference);

  return difference;
}

inline bool Date::operator<(const Date &other) const {
  auto *difference = mgp::MemHandlerCallback(date_diff, ptr_, other.ptr_);
  auto is_less = (mgp::duration_get_microseconds(difference) < 0);
  mgp::duration_destroy(difference);

  return is_less;
}

inline std::string Date::ToString() const { return std::format("{:04d}-{:02d}-{:02d}", Year(), Month(), Day()); }

// LocalTime:

inline LocalTime::LocalTime(mgp_local_time *ptr) : ptr_(mgp::MemHandlerCallback(local_time_copy, ptr)) {}

inline LocalTime::LocalTime(const mgp_local_time *const_ptr)
    : ptr_(mgp::MemHandlerCallback(local_time_copy, const_cast<mgp_local_time *>(const_ptr))) {}

inline LocalTime::LocalTime(std::string_view string)
    : ptr_(mgp::MemHandlerCallback(local_time_from_string, string.data())) {}

inline LocalTime::LocalTime(int hour, int minute, int second, int millisecond, int microsecond) {
  mgp_local_time_parameters params{
      .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = microsecond};
  ptr_ = mgp::MemHandlerCallback(local_time_from_parameters, &params);
}

inline LocalTime::LocalTime(const LocalTime &other) : LocalTime(other.ptr_) {}

inline LocalTime::LocalTime(LocalTime &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; };

inline LocalTime &LocalTime::operator=(const LocalTime &other) {
  if (this != &other) {
    mgp::local_time_destroy(ptr_);

    ptr_ = mgp::MemHandlerCallback(local_time_copy, other.ptr_);
  }
  return *this;
}

inline LocalTime &LocalTime::operator=(LocalTime &&other) noexcept {
  if (this != &other) {
    mgp::local_time_destroy(ptr_);

    ptr_ = other.ptr_;
    other.ptr_ = nullptr;
  }
  return *this;
}

inline LocalTime::~LocalTime() {
  if (ptr_ != nullptr) {
    mgp::local_time_destroy(ptr_);
  }
}

inline LocalTime LocalTime::Now() {
  auto *mgp_local_time = mgp::MemHandlerCallback(local_time_now);
  auto local_time = LocalTime(mgp_local_time);
  mgp::local_time_destroy(mgp_local_time);

  return local_time;
}

inline int LocalTime::Hour() const { return mgp::local_time_get_hour(ptr_); }

inline int LocalTime::Minute() const { return mgp::local_time_get_minute(ptr_); }

inline int LocalTime::Second() const { return mgp::local_time_get_second(ptr_); }

inline int LocalTime::Millisecond() const { return mgp::local_time_get_millisecond(ptr_); }

inline int LocalTime::Microsecond() const { return mgp::local_time_get_microsecond(ptr_); }

inline int64_t LocalTime::Timestamp() const { return mgp::local_time_timestamp(ptr_); }

inline bool LocalTime::operator==(const LocalTime &other) const { return util::LocalTimesEqual(ptr_, other.ptr_); }

inline LocalTime LocalTime::operator+(const Duration &dur) const {
  auto *mgp_sum = mgp::MemHandlerCallback(local_time_add_duration, ptr_, dur.ptr_);
  auto sum = LocalTime(mgp_sum);
  mgp::local_time_destroy(mgp_sum);

  return sum;
}

inline LocalTime LocalTime::operator-(const Duration &dur) const {
  auto *mgp_difference = mgp::MemHandlerCallback(local_time_sub_duration, ptr_, dur.ptr_);
  auto difference = LocalTime(mgp_difference);
  mgp::local_time_destroy(mgp_difference);

  return difference;
}

inline Duration LocalTime::operator-(const LocalTime &other) const {
  auto *mgp_difference = mgp::MemHandlerCallback(local_time_diff, ptr_, other.ptr_);
  auto difference = Duration(mgp_difference);
  mgp::duration_destroy(mgp_difference);

  return difference;
}

inline bool LocalTime::operator<(const LocalTime &other) const {
  auto *difference = mgp::MemHandlerCallback(local_time_diff, ptr_, other.ptr_);
  auto is_less = (mgp::duration_get_microseconds(difference) < 0);
  mgp::duration_destroy(difference);

  return is_less;
}

inline std::string LocalTime::ToString() const {
  return std::format("{:02d}:{:02d}:{:02d}.{:03d}{:03d}", Hour(), Minute(), Second(), Millisecond(), Microsecond());
}

// LocalDateTime:

inline LocalDateTime::LocalDateTime(mgp_local_date_time *ptr)
    : ptr_(mgp::MemHandlerCallback(local_date_time_copy, ptr)) {}

inline LocalDateTime::LocalDateTime(const mgp_local_date_time *const_ptr)
    : ptr_(mgp::MemHandlerCallback(local_date_time_copy, const_cast<mgp_local_date_time *>(const_ptr))) {}

inline LocalDateTime::LocalDateTime(std::string_view string)
    : ptr_(mgp::MemHandlerCallback(local_date_time_from_string, string.data())) {}

inline LocalDateTime::LocalDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond,
                                    int microsecond) {
  struct mgp_date_parameters date_params{.year = year, .month = month, .day = day};
  struct mgp_local_time_parameters local_time_params{
      .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = microsecond};
  mgp_local_date_time_parameters params{.date_parameters = &date_params, .local_time_parameters = &local_time_params};
  ptr_ = mgp::MemHandlerCallback(local_date_time_from_parameters, &params);
}

inline LocalDateTime::LocalDateTime(const LocalDateTime &other) : LocalDateTime(other.ptr_) {}

inline LocalDateTime::LocalDateTime(LocalDateTime &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; };

inline ZonedDateTime::ZonedDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond,
                                    int microsecond, int offset_in_minutes) {
  struct mgp_date_parameters date_params{.year = year, .month = month, .day = day};
  struct mgp_local_time_parameters local_time_params{
      .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = microsecond};
  mgp_zoned_date_time_parameters params{.date_parameters = &date_params,
                                        .local_time_parameters = &local_time_params,
                                        .timezone_info = {.offset_in_minutes = offset_in_minutes},
                                        .is_named_timezone = 0};
  ptr_ = mgp::MemHandlerCallback(zoned_date_time_from_parameters, &params);
}

inline ZonedDateTime::ZonedDateTime(int year, int month, int day, int hour, int minute, int second, int millisecond,
                                    int microsecond, std::string_view timezone_name) {
  struct mgp_date_parameters date_params{.year = year, .month = month, .day = day};
  struct mgp_local_time_parameters local_time_params{
      .hour = hour, .minute = minute, .second = second, .millisecond = millisecond, .microsecond = microsecond};
  mgp_zoned_date_time_parameters params{.date_parameters = &date_params,
                                        .local_time_parameters = &local_time_params,
                                        .timezone_info = {.timezone_name = timezone_name.data()},
                                        .is_named_timezone = 1};
  ptr_ = mgp::MemHandlerCallback(zoned_date_time_from_parameters, &params);
}

inline LocalDateTime &LocalDateTime::operator=(const LocalDateTime &other) {
  if (this != &other) {
    mgp::local_date_time_destroy(ptr_);

    ptr_ = mgp::MemHandlerCallback(local_date_time_copy, other.ptr_);
  }
  return *this;
}

inline LocalDateTime &LocalDateTime::operator=(LocalDateTime &&other) noexcept {
  if (this != &other) {
    mgp::local_date_time_destroy(ptr_);

    ptr_ = other.ptr_;
    other.ptr_ = nullptr;
  }
  return *this;
}

inline LocalDateTime::~LocalDateTime() {
  if (ptr_ != nullptr) {
    mgp::local_date_time_destroy(ptr_);
  }
}

inline LocalDateTime LocalDateTime::Now() {
  auto *mgp_local_date_time = mgp::MemHandlerCallback(local_date_time_now);
  auto local_date_time = LocalDateTime(mgp_local_date_time);
  mgp::local_date_time_destroy(mgp_local_date_time);

  return local_date_time;
}

inline int LocalDateTime::Year() const { return mgp::local_date_time_get_year(ptr_); }

inline int LocalDateTime::Month() const { return mgp::local_date_time_get_month(ptr_); }

inline int LocalDateTime::Day() const { return mgp::local_date_time_get_day(ptr_); }

inline int LocalDateTime::Hour() const { return mgp::local_date_time_get_hour(ptr_); }

inline int LocalDateTime::Minute() const { return mgp::local_date_time_get_minute(ptr_); }

inline int LocalDateTime::Second() const { return mgp::local_date_time_get_second(ptr_); }

inline int LocalDateTime::Millisecond() const { return mgp::local_date_time_get_millisecond(ptr_); }

inline int LocalDateTime::Microsecond() const { return mgp::local_date_time_get_microsecond(ptr_); }

inline int64_t LocalDateTime::Timestamp() const { return mgp::local_date_time_timestamp(ptr_); }

inline bool LocalDateTime::operator==(const LocalDateTime &other) const {
  return util::LocalDateTimesEqual(ptr_, other.ptr_);
}

inline LocalDateTime LocalDateTime::operator+(const Duration &dur) const {
  auto *mgp_sum = mgp::MemHandlerCallback(local_date_time_add_duration, ptr_, dur.ptr_);
  auto sum = LocalDateTime(mgp_sum);
  mgp::local_date_time_destroy(mgp_sum);

  return sum;
}

inline LocalDateTime LocalDateTime::operator-(const Duration &dur) const {
  auto *mgp_difference = mgp::MemHandlerCallback(local_date_time_sub_duration, ptr_, dur.ptr_);
  auto difference = LocalDateTime(mgp_difference);
  mgp::local_date_time_destroy(mgp_difference);

  return difference;
}

inline Duration LocalDateTime::operator-(const LocalDateTime &other) const {
  auto *mgp_difference = mgp::MemHandlerCallback(local_date_time_diff, ptr_, other.ptr_);
  auto difference = Duration(mgp_difference);
  mgp::duration_destroy(mgp_difference);

  return difference;
}

inline bool LocalDateTime::operator<(const LocalDateTime &other) const {
  auto *difference = mgp::MemHandlerCallback(local_date_time_diff, ptr_, other.ptr_);
  auto is_less = (mgp::duration_get_microseconds(difference) < 0);
  mgp::duration_destroy(difference);

  return is_less;
}

inline std::string LocalDateTime::ToString() const {
  return std::format("{:04d}-{:02d}-{:02d}T{:02d}:{:02d}:{:02d}.{:03d}{:03d}",
                     Year(),
                     Month(),
                     Day(),
                     Hour(),
                     Minute(),
                     Second(),
                     Millisecond(),
                     Microsecond());
}

// Duration:

inline Duration::Duration(mgp_duration *ptr) : ptr_(mgp::MemHandlerCallback(duration_copy, ptr)) {}

inline Duration::Duration(const mgp_duration *const_ptr)
    : ptr_(mgp::MemHandlerCallback(duration_copy, const_cast<mgp_duration *>(const_ptr))) {}

inline Duration::Duration(std::string_view string)
    : ptr_(mgp::MemHandlerCallback(duration_from_string, string.data())) {}

inline Duration::Duration(int64_t microseconds)
    : ptr_(mgp::MemHandlerCallback(duration_from_microseconds, microseconds)) {}

inline Duration::Duration(double day, double hour, double minute, double second, double millisecond,
                          double microsecond) {
  mgp_duration_parameters params{.day = day,
                                 .hour = hour,
                                 .minute = minute,
                                 .second = second,
                                 .millisecond = millisecond,
                                 .microsecond = microsecond};
  ptr_ = mgp::MemHandlerCallback(duration_from_parameters, &params);
}

inline Duration::Duration(const Duration &other) : Duration(other.ptr_) {}

inline Duration::Duration(Duration &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; };

inline Duration &Duration::operator=(const Duration &other) {
  if (this != &other) {
    mgp::duration_destroy(ptr_);

    ptr_ = mgp::MemHandlerCallback(duration_copy, other.ptr_);
  }
  return *this;
}

inline Duration &Duration::operator=(Duration &&other) noexcept {
  if (this != &other) {
    mgp::duration_destroy(ptr_);

    ptr_ = other.ptr_;
    other.ptr_ = nullptr;
  }
  return *this;
}

inline Duration::~Duration() {
  if (ptr_ != nullptr) {
    mgp::duration_destroy(ptr_);
  }
}

inline int64_t Duration::Microseconds() const { return mgp::duration_get_microseconds(ptr_); }

inline bool Duration::operator==(const Duration &other) const { return util::DurationsEqual(ptr_, other.ptr_); }

inline Duration Duration::operator+(const Duration &other) const {
  auto *mgp_sum = mgp::MemHandlerCallback(duration_add, ptr_, other.ptr_);
  auto sum = Duration(mgp_sum);
  mgp::duration_destroy(mgp_sum);

  return sum;
}

inline Duration Duration::operator-(const Duration &other) const {
  auto *mgp_difference = mgp::MemHandlerCallback(duration_sub, ptr_, other.ptr_);
  auto difference = Duration(mgp_difference);
  mgp::duration_destroy(mgp_difference);

  return difference;
}

inline Duration Duration::operator-() const {
  auto *mgp_neg = mgp::MemHandlerCallback(duration_neg, ptr_);
  auto neg = Duration(mgp_neg);
  mgp::duration_destroy(mgp_neg);

  return neg;
}

inline bool Duration::operator<(const Duration &other) const {
  auto *difference = mgp::MemHandlerCallback(duration_sub, ptr_, other.ptr_);
  auto is_less = (mgp::duration_get_microseconds(difference) < 0);
  mgp::duration_destroy(difference);

  return is_less;
}

inline std::string Duration::ToString() const { return std::format("{}ms", Microseconds()); }

// ZonedDateTime:

inline ZonedDateTime::ZonedDateTime(mgp_zoned_date_time *ptr)
    : ptr_(mgp::MemHandlerCallback(zoned_date_time_copy, ptr)) {}

inline ZonedDateTime::ZonedDateTime(const mgp_zoned_date_time *const_ptr)
    : ptr_(mgp::MemHandlerCallback(zoned_date_time_copy, const_cast<mgp_zoned_date_time *>(const_ptr))) {}

inline ZonedDateTime::ZonedDateTime(std::string_view string)
    : ptr_(mgp::MemHandlerCallback(zoned_date_time_from_string, string.data())) {}

inline ZonedDateTime::ZonedDateTime(const ZonedDateTime &other) : ZonedDateTime(other.ptr_) {}

inline ZonedDateTime::ZonedDateTime(ZonedDateTime &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; };

inline ZonedDateTime::~ZonedDateTime() {
  if (ptr_ != nullptr) {
    mgp::zoned_date_time_destroy(ptr_);
  }
}

inline int ZonedDateTime::Year() const { return mgp::zoned_date_time_get_year(ptr_); }

inline int ZonedDateTime::Month() const { return mgp::zoned_date_time_get_month(ptr_); }

inline int ZonedDateTime::Day() const { return mgp::zoned_date_time_get_day(ptr_); }

inline int ZonedDateTime::Hour() const { return mgp::zoned_date_time_get_hour(ptr_); }

inline int ZonedDateTime::Minute() const { return mgp::zoned_date_time_get_minute(ptr_); }

inline int ZonedDateTime::Second() const { return mgp::zoned_date_time_get_second(ptr_); }

inline int ZonedDateTime::Millisecond() const { return mgp::zoned_date_time_get_millisecond(ptr_); }

inline int ZonedDateTime::Microsecond() const { return mgp::zoned_date_time_get_microsecond(ptr_); }

inline int64_t ZonedDateTime::Timestamp() const { return mgp::zoned_date_time_timestamp(ptr_); }

inline char const *ZonedDateTime::Timezone() const { return mgp::zoned_date_time_get_timezone(ptr_); }

inline int ZonedDateTime::Offset() const { return mgp::zoned_date_time_get_offset(ptr_); }

inline bool ZonedDateTime::operator==(const ZonedDateTime &other) const {
  return util::ZonedDateTimesEqual(ptr_, other.ptr_);
}

inline std::string ZonedDateTime::ToString() const {
  auto tp = std::chrono::sys_time<std::chrono::microseconds>{std::chrono::microseconds{Timestamp()}};
  if (!std::string_view{Timezone()}.empty()) {
    auto tz_ptr = std::chrono::locate_zone(Timezone());
    auto zt = std::chrono::zoned_time{tz_ptr, tp};
    return std::format("{0:%Y}-{0:%m}-{0:%d}T{0:%H}:{0:%M}:{0:%S}{0:%Ez}[{1}]", zt, Timezone());
  } else {
    auto local_tp = std::chrono::sys_time<std::chrono::microseconds>{std::chrono::microseconds{Timestamp()} +
                                                                     std::chrono::minutes{Offset()}};
    auto offset_mins = Offset();
    auto hours = offset_mins / 60;
    auto mins = std::abs(offset_mins % 60);
    auto offset_str = std::format("{:+03d}:{:02d}", hours, mins);
    return std::format("{0:%Y}-{0:%m}-{0:%d}T{0:%H}:{0:%M}:{0:%S}{1}", local_tp, offset_str);
  }
}

inline ZonedDateTime ZonedDateTime::operator+(const Duration &dur) const {
  auto *mgp_sum = mgp::MemHandlerCallback(zoned_date_time_add_duration, ptr_, dur.ptr_);
  auto sum = ZonedDateTime(mgp_sum);
  mgp::zoned_date_time_destroy(mgp_sum);

  return sum;
}

inline ZonedDateTime ZonedDateTime::operator-(const Duration &dur) const {
  auto *mgp_difference = mgp::MemHandlerCallback(zoned_date_time_sub_duration, ptr_, dur.ptr_);
  auto difference = ZonedDateTime(mgp_difference);
  mgp::zoned_date_time_destroy(mgp_difference);

  return difference;
}

inline Duration ZonedDateTime::operator-(const ZonedDateTime &other) const {
  auto *mgp_difference = mgp::MemHandlerCallback(zoned_date_time_diff, ptr_, other.ptr_);
  auto difference = Duration(mgp_difference);
  mgp::duration_destroy(mgp_difference);

  return difference;
}

inline ZonedDateTime ZonedDateTime::Now() {
  auto *mgp_zoned_date_time = mgp::MemHandlerCallback(zoned_date_time_now);
  auto zoned_date_time = ZonedDateTime(mgp_zoned_date_time);
  mgp::zoned_date_time_destroy(mgp_zoned_date_time);

  return zoned_date_time;
}

/* #endregion */

/* #endregion */

/* #region Point2d */

inline Point2d::Point2d(mgp_point_2d *ptr) : ptr_(mgp::MemHandlerCallback(point_2d_copy, ptr)) {}

inline Point2d::Point2d(const mgp_point_2d *const_ptr)
    : ptr_(mgp::MemHandlerCallback(point_2d_copy, const_cast<mgp_point_2d *>(const_ptr))) {}

inline Point2d::Point2d(const Point2d &other) : Point2d(other.ptr_) {}

inline Point2d::Point2d(Point2d &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }

inline Point2d &Point2d::operator=(const Point2d &other) {
  if (this != &other) {
    mgp::point_2d_destroy(ptr_);
    ptr_ = mgp::MemHandlerCallback(point_2d_copy, other.ptr_);
  }
  return *this;
}

inline Point2d &Point2d::operator=(Point2d &&other) noexcept {
  if (this != &other) {
    mgp::point_2d_destroy(ptr_);
    ptr_ = other.ptr_;
    other.ptr_ = nullptr;
  }
  return *this;
}

inline Point2d::Point2d(double x, double y, uint16_t srid) {
  ptr_ = mgp::MemHandlerCallback(point_2d_make, x, y, srid);
}

inline Point2d::~Point2d() {
  if (ptr_ != nullptr) {
    mgp::point_2d_destroy(ptr_);
  }
}

inline double Point2d::X() const { return mgp::point_2d_get_x(ptr_); }

inline double Point2d::Y() const { return mgp::point_2d_get_y(ptr_); }

inline uint16_t Point2d::Srid() const { return mgp::point_2d_get_srid(ptr_); }

inline bool Point2d::operator==(const Point2d &other) const { return util::Point2dsEqual(ptr_, other.ptr_); }

inline bool Point2d::operator!=(const Point2d &other) const { return !(*this == other); }

inline std::string Point2d::ToString() const { return std::format("Point2d(x={}, y={}, srid={})", X(), Y(), Srid()); }

/* #endregion */

/* #region Point3d */

inline Point3d::Point3d(mgp_point_3d *ptr) : ptr_(mgp::MemHandlerCallback(point_3d_copy, ptr)) {}

inline Point3d::Point3d(const mgp_point_3d *const_ptr)
    : ptr_(mgp::MemHandlerCallback(point_3d_copy, const_cast<mgp_point_3d *>(const_ptr))) {}

inline Point3d::Point3d(const Point3d &other) : Point3d(other.ptr_) {}

inline Point3d::Point3d(Point3d &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }

inline Point3d &Point3d::operator=(const Point3d &other) {
  if (this != &other) {
    mgp::point_3d_destroy(ptr_);
    ptr_ = mgp::MemHandlerCallback(point_3d_copy, other.ptr_);
  }
  return *this;
}

inline Point3d &Point3d::operator=(Point3d &&other) noexcept {
  if (this != &other) {
    mgp::point_3d_destroy(ptr_);
    ptr_ = other.ptr_;
    other.ptr_ = nullptr;
  }
  return *this;
}

inline Point3d::Point3d(double x, double y, double z, uint16_t srid) {
  ptr_ = mgp::MemHandlerCallback(point_3d_make, x, y, z, srid);
}

inline Point3d::~Point3d() {
  if (ptr_ != nullptr) {
    mgp::point_3d_destroy(ptr_);
  }
}

inline double Point3d::X() const { return mgp::point_3d_get_x(ptr_); }

inline double Point3d::Y() const { return mgp::point_3d_get_y(ptr_); }

inline double Point3d::Z() const { return mgp::point_3d_get_z(ptr_); }

inline uint16_t Point3d::Srid() const { return mgp::point_3d_get_srid(ptr_); }

inline bool Point3d::operator==(const Point3d &other) const { return util::Point3dsEqual(ptr_, other.ptr_); }

inline bool Point3d::operator!=(const Point3d &other) const { return !(*this == other); }

inline std::string Point3d::ToString() const {
  return std::format("Point3d(x={}, y={}, z={}, srid={})", X(), Y(), Z(), Srid());
}

/* #endregion */

/* #region Enum */

inline Enum::Enum(mgp_enum *ptr) : ptr_(mgp::MemHandlerCallback(enum_copy, ptr)) {}

inline Enum::Enum(const mgp_enum *const_ptr)
    : ptr_(mgp::MemHandlerCallback(enum_copy, const_cast<mgp_enum *>(const_ptr))) {}

inline Enum::Enum(const Enum &other) : Enum(other.ptr_) {}

inline Enum::Enum(Enum &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }

inline Enum &Enum::operator=(const Enum &other) {
  if (this != &other) {
    mgp::enum_destroy(ptr_);
    ptr_ = mgp::MemHandlerCallback(enum_copy, other.ptr_);
  }
  return *this;
}

inline Enum &Enum::operator=(Enum &&other) noexcept {
  if (this != &other) {
    mgp::enum_destroy(ptr_);
    ptr_ = other.ptr_;
    other.ptr_ = nullptr;
  }
  return *this;
}

inline Enum::Enum(std::string_view type_name, std::string_view value_name) {
  // enum_make takes const char* (null-terminated). Materialise into std::string
  // to guarantee null-termination when the caller passes a non-terminated view.
  const std::string tn{type_name};
  const std::string vn{value_name};
  ptr_ = mgp::MemHandlerCallback(enum_make, tn.c_str(), vn.c_str());
}

inline Enum::~Enum() {
  if (ptr_ != nullptr) {
    mgp::enum_destroy(ptr_);
  }
}

inline std::string_view Enum::TypeName() const { return mgp::enum_get_type_name(ptr_); }

inline std::string_view Enum::ValueName() const { return mgp::enum_get_value_name(ptr_); }

inline bool Enum::operator==(const Enum &other) const { return util::EnumsEqual(ptr_, other.ptr_); }

inline bool Enum::operator!=(const Enum &other) const { return !(*this == other); }

inline std::string Enum::ToString() const { return std::format("{}::{}", TypeName(), ValueName()); }

/* #endregion */

/* #region Value */

inline Value::Value(mgp_value *ptr) : ptr_(mgp::MemHandlerCallback(value_copy, ptr)) {}

inline Value::Value(StealType /*steal*/, mgp_value *ptr) : ptr_{ptr} {}

inline Value::Value(RefType /*ref*/, mgp_value *ptr)
    : ptr_(reinterpret_cast<mgp_value *>(reinterpret_cast<uintptr_t>(ptr) | uintptr_t{1})) {}

inline Value::Value() : ptr_(mgp::MemHandlerCallback(value_make_null)) {}

inline Value::Value(const bool value) : ptr_(mgp::MemHandlerCallback(value_make_bool, value)) {}

inline Value::Value(const int64_t value) : ptr_(mgp::MemHandlerCallback(value_make_int, value)) {}

inline Value::Value(const double value) : ptr_(mgp::MemHandlerCallback(value_make_double, value)) {}

inline Value::Value(const char *value) : ptr_(mgp::MemHandlerCallback(value_make_string, value)) {}

inline Value::Value(const std::string_view value) : ptr_(mgp::MemHandlerCallback(value_make_string, value.data())) {}

inline Value::Value(const List &list) : ptr_(mgp::value_make_list(mgp::MemHandlerCallback(list_copy, list.ptr_))) {}

inline Value::Value(List &&list) {
  ptr_ = mgp::value_make_list(list.ptr_);
  list.ptr_ = nullptr;
}

inline Value::Value(const Map &map) : ptr_(mgp::value_make_map(mgp::MemHandlerCallback(map_copy, map.ptr_))) {}

inline Value::Value(Map &&map) {
  ptr_ = mgp::value_make_map(map.ptr_);
  map.ptr_ = nullptr;
}

inline Value::Value(const Node &node) : ptr_(mgp::value_make_vertex(mgp::MemHandlerCallback(vertex_copy, node.ptr_))) {}

inline Value::Value(Node &&node) {
  ptr_ = mgp::value_make_vertex(const_cast<mgp_vertex *>(node.ptr_));
  node.ptr_ = nullptr;
}

inline Value::Value(const Relationship &relationship)
    : ptr_(mgp::value_make_edge(mgp::MemHandlerCallback(edge_copy, relationship.ptr_))) {}

inline Value::Value(Relationship &&relationship) {
  ptr_ = mgp::value_make_edge(const_cast<mgp_edge *>(relationship.ptr_));
  relationship.ptr_ = nullptr;
}

inline Value::Value(const Path &path) : ptr_(mgp::value_make_path(mgp::MemHandlerCallback(path_copy, path.ptr_))) {}

inline Value::Value(Path &&path) {
  ptr_ = mgp::value_make_path(path.ptr_);
  path.ptr_ = nullptr;
}

inline Value::Value(const Date &date) : ptr_(mgp::value_make_date(mgp::MemHandlerCallback(date_copy, date.ptr_))) {}

inline Value::Value(Date &&date) {
  ptr_ = mgp::value_make_date(date.ptr_);
  date.ptr_ = nullptr;
}

inline Value::Value(const LocalTime &local_time)
    : ptr_(mgp::value_make_local_time(mgp::MemHandlerCallback(local_time_copy, local_time.ptr_))) {}

inline Value::Value(LocalTime &&local_time) {
  ptr_ = mgp::value_make_local_time(local_time.ptr_);
  local_time.ptr_ = nullptr;
}

inline Value::Value(const LocalDateTime &local_date_time)
    : ptr_(mgp::value_make_local_date_time(mgp::MemHandlerCallback(local_date_time_copy, local_date_time.ptr_))) {}

inline Value::Value(LocalDateTime &&local_date_time) {
  ptr_ = mgp::value_make_local_date_time(local_date_time.ptr_);
  local_date_time.ptr_ = nullptr;
}

inline Value::Value(const Duration &duration)
    : ptr_(mgp::value_make_duration(mgp::MemHandlerCallback(duration_copy, duration.ptr_))) {}

inline Value::Value(Duration &&duration) {
  ptr_ = mgp::value_make_duration(duration.ptr_);
  duration.ptr_ = nullptr;
}

inline Value::Value(const ZonedDateTime &zoned_date_time)
    : ptr_(mgp::value_make_zoned_date_time(mgp::MemHandlerCallback(zoned_date_time_copy, zoned_date_time.ptr_))) {}

inline Value::Value(ZonedDateTime &&zoned_date_time) {
  ptr_ = mgp::value_make_zoned_date_time(zoned_date_time.ptr_);
  zoned_date_time.ptr_ = nullptr;
}

inline Value::Value(const Point2d &point)
    : ptr_(mgp::value_make_point_2d(mgp::MemHandlerCallback(point_2d_copy, point.ptr_))) {}

inline Value::Value(Point2d &&point) {
  ptr_ = mgp::value_make_point_2d(point.ptr_);
  point.ptr_ = nullptr;
}

inline Value::Value(const Point3d &point)
    : ptr_(mgp::value_make_point_3d(mgp::MemHandlerCallback(point_3d_copy, point.ptr_))) {}

inline Value::Value(Point3d &&point) {
  ptr_ = mgp::value_make_point_3d(point.ptr_);
  point.ptr_ = nullptr;
}

inline Value::Value(const Enum &enum_v) : ptr_(mgp::value_make_enum(mgp::MemHandlerCallback(enum_copy, enum_v.ptr_))) {}

inline Value::Value(Enum &&enum_v) {
  ptr_ = mgp::value_make_enum(enum_v.ptr_);
  enum_v.ptr_ = nullptr;
}

inline Value::Value(const Value &other) : Value(other.ptr()) {}

inline Value::Value(Value &&other) noexcept : ptr_(other.ptr_) { other.ptr_ = nullptr; }

inline Value &Value::operator=(const Value &other) {
  if (this != &other) {
    if (!IsRef()) {
      mgp::value_destroy(ptr_);
    }

    ptr_ = mgp::MemHandlerCallback(value_copy, other.ptr());
  }
  return *this;
}

inline Value &Value::operator=(Value &&other) noexcept {
  if (this != &other) {
    if (!IsRef()) {
      mgp::value_destroy(ptr_);
    }

    ptr_ = other.ptr_;
    other.ptr_ = nullptr;
  }
  return *this;
}

inline Value::~Value() {
  if (ptr_ != nullptr && !IsRef()) {
    mgp::value_destroy(ptr_);
  }
}

inline bool Value::IsRef() const { return reinterpret_cast<uintptr_t>(ptr_) & uintptr_t{1}; }

inline mgp_value *Value::ptr() const {
  return reinterpret_cast<mgp_value *>(reinterpret_cast<uintptr_t>(ptr_) & ~uintptr_t{1});
}

inline mgp::Type Value::Type() const { return util::ToAPIType(mgp::value_get_type(this->ptr())); }

inline bool Value::ValueBool() const {
  if (Type() != Type::Bool) {
    throw ValueException("Type of value is wrong: expected Bool.");
  }
  return mgp::value_get_bool(this->ptr());
}

inline bool Value::ValueBool() {
  if (Type() != Type::Bool) {
    throw ValueException("Type of value is wrong: expected Bool.");
  }
  return mgp::value_get_bool(this->ptr());
}

inline std::int64_t Value::ValueInt() const {
  if (Type() != Type::Int) {
    throw ValueException("Type of value is wrong: expected Int.");
  }
  return mgp::value_get_int(this->ptr());
}

inline std::int64_t Value::ValueInt() {
  if (Type() != Type::Int) {
    throw ValueException("Type of value is wrong: expected Int.");
  }
  return mgp::value_get_int(this->ptr());
}

inline double Value::ValueDouble() const {
  if (Type() != Type::Double) {
    throw ValueException("Type of value is wrong: expected Double.");
  }
  return mgp::value_get_double(this->ptr());
}

inline double Value::ValueDouble() {
  if (Type() != Type::Double) {
    throw ValueException("Type of value is wrong: expected Double.");
  }
  return mgp::value_get_double(this->ptr());
}

inline double Value::ValueNumeric() const {
  if (Type() != Type::Int && Type() != Type::Double) {
    throw ValueException("Type of value is wrong: expected Int or Double.");
  }
  if (Type() == Type::Int) {
    return static_cast<double>(mgp::value_get_int(this->ptr()));
  }
  return mgp::value_get_double(this->ptr());
}

inline double Value::ValueNumeric() {
  if (Type() != Type::Int && Type() != Type::Double) {
    throw ValueException("Type of value is wrong: expected Int or Double.");
  }
  if (Type() == Type::Int) {
    return static_cast<double>(mgp::value_get_int(this->ptr()));
  }
  return mgp::value_get_double(this->ptr());
}

inline std::string_view Value::ValueString() const {
  if (Type() != Type::String) {
    throw ValueException("Type of value is wrong: expected String.");
  }
  return mgp::value_get_string(this->ptr());
}

inline std::string_view Value::ValueString() {
  if (Type() != Type::String) {
    throw ValueException("Type of value is wrong: expected String.");
  }
  return mgp::value_get_string(this->ptr());
}

inline List Value::ValueList() const {
  if (Type() != Type::List) {
    throw ValueException("Type of value is wrong: expected List.");
  }
  return List(mgp::value_get_list(this->ptr()));
}

inline List Value::ValueList() {
  if (Type() != Type::List) {
    throw ValueException("Type of value is wrong: expected List.");
  }
  return List(mgp::value_get_list(this->ptr()));
}

inline Map Value::ValueMap() const {
  if (Type() != Type::Map) {
    throw ValueException("Type of value is wrong: expected Map.");
  }
  return Map(mgp::value_get_map(this->ptr()));
}

inline Map Value::ValueMap() {
  if (Type() != Type::Map) {
    throw ValueException("Type of value is wrong: expected Map.");
  }
  return Map(mgp::value_get_map(this->ptr()));
}

inline Node Value::ValueNode() const {
  if (Type() != Type::Node) {
    throw ValueException("Type of value is wrong: expected Node.");
  }
  return Node(mgp::value_get_vertex(this->ptr()));
}

inline Node Value::ValueNode() {
  if (Type() != Type::Node) {
    throw ValueException("Type of value is wrong: expected Node.");
  }
  return Node(mgp::value_get_vertex(this->ptr()));
}

inline Relationship Value::ValueRelationship() const {
  if (Type() != Type::Relationship) {
    throw ValueException("Type of value is wrong: expected Relationship.");
  }
  return Relationship(mgp::value_get_edge(this->ptr()));
}

inline Relationship Value::ValueRelationship() {
  if (Type() != Type::Relationship) {
    throw ValueException("Type of value is wrong: expected Relationship.");
  }
  return Relationship(mgp::value_get_edge(this->ptr()));
}

inline Path Value::ValuePath() const {
  if (Type() != Type::Path) {
    throw ValueException("Type of value is wrong: expected Path.");
  }
  return Path(mgp::value_get_path(this->ptr()));
}

inline Path Value::ValuePath() {
  if (Type() != Type::Path) {
    throw ValueException("Type of value is wrong: expected Path.");
  }
  return Path(mgp::value_get_path(this->ptr()));
}

inline Date Value::ValueDate() const {
  if (Type() != Type::Date) {
    throw ValueException("Type of value is wrong: expected Date.");
  }
  return Date(mgp::value_get_date(this->ptr()));
}

inline Date Value::ValueDate() {
  if (Type() != Type::Date) {
    throw ValueException("Type of value is wrong: expected Date.");
  }
  return Date(mgp::value_get_date(this->ptr()));
}

inline LocalTime Value::ValueLocalTime() const {
  if (Type() != Type::LocalTime) {
    throw ValueException("Type of value is wrong: expected LocalTime.");
  }
  return LocalTime(mgp::value_get_local_time(this->ptr()));
}

inline LocalTime Value::ValueLocalTime() {
  if (Type() != Type::LocalTime) {
    throw ValueException("Type of value is wrong: expected LocalTime.");
  }
  return LocalTime(mgp::value_get_local_time(this->ptr()));
}

inline LocalDateTime Value::ValueLocalDateTime() const {
  if (Type() != Type::LocalDateTime) {
    throw ValueException("Type of value is wrong: expected LocalDateTime.");
  }
  return LocalDateTime(mgp::value_get_local_date_time(this->ptr()));
}

inline LocalDateTime Value::ValueLocalDateTime() {
  if (Type() != Type::LocalDateTime) {
    throw ValueException("Type of value is wrong: expected LocalDateTime.");
  }
  return LocalDateTime(mgp::value_get_local_date_time(this->ptr()));
}

inline Duration Value::ValueDuration() const {
  if (Type() != Type::Duration) {
    throw ValueException("Type of value is wrong: expected Duration.");
  }
  return Duration(mgp::value_get_duration(this->ptr()));
}

inline Duration Value::ValueDuration() {
  if (Type() != Type::Duration) {
    throw ValueException("Type of value is wrong: expected Duration.");
  }
  return Duration(mgp::value_get_duration(this->ptr()));
}

inline ZonedDateTime Value::ValueZonedDateTime() const {
  if (Type() != Type::ZonedDateTime) {
    throw ValueException("Type of value is wrong: expected ZonedDateTime.");
  }
  return ZonedDateTime(mgp::value_get_zoned_date_time(this->ptr()));
}

inline ZonedDateTime Value::ValueZonedDateTime() {
  if (Type() != Type::ZonedDateTime) {
    throw ValueException("Type of value is wrong: expected ZonedDateTime.");
  }
  return ZonedDateTime(mgp::value_get_zoned_date_time(this->ptr()));
}

inline Point2d Value::ValuePoint2d() const {
  if (Type() != Type::Point2d) {
    throw ValueException("Type of value is wrong: expected Point2d.");
  }
  return Point2d(mgp::value_get_point_2d(this->ptr()));
}

inline Point2d Value::ValuePoint2d() {
  if (Type() != Type::Point2d) {
    throw ValueException("Type of value is wrong: expected Point2d.");
  }
  return Point2d(mgp::value_get_point_2d(this->ptr()));
}

inline Point3d Value::ValuePoint3d() const {
  if (Type() != Type::Point3d) {
    throw ValueException("Type of value is wrong: expected Point3d.");
  }
  return Point3d(mgp::value_get_point_3d(this->ptr()));
}

inline Point3d Value::ValuePoint3d() {
  if (Type() != Type::Point3d) {
    throw ValueException("Type of value is wrong: expected Point3d.");
  }
  return Point3d(mgp::value_get_point_3d(this->ptr()));
}

inline Enum Value::ValueEnum() const {
  if (Type() != Type::Enum) {
    throw ValueException("Type of value is wrong: expected Enum.");
  }
  return Enum(mgp::value_get_enum(this->ptr()));
}

inline Enum Value::ValueEnum() {
  if (Type() != Type::Enum) {
    throw ValueException("Type of value is wrong: expected Enum.");
  }
  return Enum(mgp::value_get_enum(this->ptr()));
}

inline bool Value::IsNull() const { return mgp::value_is_null(this->ptr()); }

inline bool Value::IsBool() const { return mgp::value_is_bool(this->ptr()); }

inline bool Value::IsInt() const { return mgp::value_is_int(this->ptr()); }

inline bool Value::IsDouble() const { return mgp::value_is_double(this->ptr()); }

inline bool Value::IsNumeric() const { return IsInt() || IsDouble(); }

inline bool Value::IsString() const { return mgp::value_is_string(this->ptr()); }

inline bool Value::IsList() const { return mgp::value_is_list(this->ptr()); }

inline bool Value::IsMap() const { return mgp::value_is_map(this->ptr()); }

inline bool Value::IsNode() const { return mgp::value_is_vertex(this->ptr()); }

inline bool Value::IsRelationship() const { return mgp::value_is_edge(this->ptr()); }

inline bool Value::IsPath() const { return mgp::value_is_path(this->ptr()); }

inline bool Value::IsDate() const { return mgp::value_is_date(this->ptr()); }

inline bool Value::IsLocalTime() const { return mgp::value_is_local_time(this->ptr()); }

inline bool Value::IsLocalDateTime() const { return mgp::value_is_local_date_time(this->ptr()); }

inline bool Value::IsDuration() const { return mgp::value_is_duration(this->ptr()); }

inline bool Value::IsZonedDateTime() const { return mgp::value_is_zoned_date_time(this->ptr()); }

inline bool Value::IsPoint2d() const { return mgp::value_is_point_2d(this->ptr()); }

inline bool Value::IsPoint3d() const { return mgp::value_is_point_3d(this->ptr()); }

inline bool Value::IsEnum() const { return mgp::value_is_enum(this->ptr()); }

inline bool Value::operator==(const Value &other) const { return util::ValuesEqual(this->ptr(), other.ptr()); }

inline bool Value::operator!=(const Value &other) const { return !(*this == other); }

inline bool Value::operator<(const Value &other) const {
  const mgp::Type &type = Type();
  if (type != other.Type() && !(IsNumeric() && other.IsNumeric())) {
    throw ValueException("Values have to be of the same type");
  }

  switch (type) {
    case Type::Null:
      throw ValueException("Cannot compare Null types");
    case Type::Bool:
      return ValueBool() < other.ValueBool();
    case Type::Int:
      return ValueNumeric() < other.ValueNumeric();
    case Type::Double:
      return ValueNumeric() < other.ValueNumeric();
    case Type::String:
      return ValueString() < other.ValueString();
    case Type::Node:
      return ValueNode() < other.ValueNode();
    case Type::Relationship:
      return ValueRelationship() < other.ValueRelationship();
    case Type::Date:
      return ValueDate() < other.ValueDate();
    case Type::LocalTime:
      return ValueLocalTime() < other.ValueLocalTime();
    case Type::LocalDateTime:
      return ValueLocalDateTime() < other.ValueLocalDateTime();
    case Type::Duration:
      return ValueDuration() < other.ValueDuration();
    case Type::Path:
    case Type::List:
    case Type::Map:
      throw ValueException("Operator < is not defined for this Path, List or Map data type");
    default:
      throw ValueException("Undefined behaviour");
  }
}

inline std::ostream &operator<<(std::ostream &os, const mgp::Value &value) {
  switch (value.Type()) {
    case mgp::Type::Null:
      return os << "null";
    case mgp::Type::Any:
      return os << "any";
    case mgp::Type::Bool:
      return os << (value.ValueBool() ? "true" : "false");
    case mgp::Type::Int:
      return os << std::to_string(value.ValueInt());
    case mgp::Type::Double:
      return os << std::to_string(value.ValueDouble());
    case mgp::Type::String:
      return os << std::string(value.ValueString());
    case mgp::Type::List:
      throw mgp::ValueException("Printing mgp::List type currently not supported.");
    case mgp::Type::Map:
      throw mgp::ValueException("Printing mgp::Map type currently not supported.");
    case mgp::Type::Node:
      return os << "Node[" + std::to_string(value.ValueNode().Id().AsInt()) + "]";
    case mgp::Type::Relationship:
      return os << "Relationship[" + std::to_string(value.ValueRelationship().Id().AsInt()) + "]";
    case mgp::Type::Path:
      throw mgp::ValueException("Printing mgp::Path type currently not supported.");
    case mgp::Type::Date:
      return os << value.ValueDate().ToString();
    case mgp::Type::LocalTime:
      return os << value.ValueLocalTime().ToString();
    case mgp::Type::LocalDateTime:
      return os << value.ValueLocalDateTime().ToString();
    case mgp::Type::Duration:
      return os << value.ValueDuration().ToString();
    case mgp::Type::ZonedDateTime:
      return os << value.ValueZonedDateTime().ToString();
    case mgp::Type::Point2d:
      return os << value.ValuePoint2d().ToString();
    case mgp::Type::Point3d:
      return os << value.ValuePoint3d().ToString();
    case mgp::Type::Enum:
      return os << value.ValueEnum().ToString();
    default:
      throw mgp::ValueException("Unknown value type");
  }
}

inline std::ostream &operator<<(std::ostream &os, const mgp::Type &type) {
  switch (type) {
    case mgp::Type::Null:
      return os << "null";
    case mgp::Type::Bool:
      return os << "bool";
    case mgp::Type::Int:
      return os << "int";
    case mgp::Type::Double:
      return os << "double";
    case mgp::Type::String:
      return os << "string";
    case mgp::Type::List:
      return os << "list";
    case mgp::Type::Map:
      return os << "map";
    case mgp::Type::Node:
      return os << "vertex";
    case mgp::Type::Relationship:
      return os << "edge";
    case mgp::Type::Path:
      return os << "path";
    case mgp::Type::Date:
      return os << "date";
    case mgp::Type::LocalTime:
      return os << "local_time";
    case mgp::Type::LocalDateTime:
      return os << "local_date_time";
    case mgp::Type::Duration:
      return os << "duration";
    case mgp::Type::ZonedDateTime:
      return os << "zoned_date_time";
    case mgp::Type::Point2d:
      return os << "point_2d";
    case mgp::Type::Point3d:
      return os << "point_3d";
    case mgp::Type::Enum:
      return os << "enum";
    default:
      throw ValueException("Unknown type");
  }
}

inline std::string Value::ToString() const {
  const mgp::Type &type = Type();
  switch (type) {
    case Type::Null:
      return "";
    case Type::Bool:
      return ValueBool() ? "true" : "false";
    case Type::Int:
      return std::to_string(ValueInt());
    case Type::Double:
      return std::to_string(ValueDouble());
    case Type::String:
      return std::string(ValueString());
    case Type::Node:
      return ValueNode().ToString();
    case Type::Relationship:
      return ValueRelationship().ToString();
    case Type::Date:
      return ValueDate().ToString();
    case Type::LocalTime:
      return ValueLocalTime().ToString();
    case Type::LocalDateTime:
      return ValueLocalDateTime().ToString();
    case Type::ZonedDateTime:
      return ValueZonedDateTime().ToString();
    case Type::Duration:
      return ValueDuration().ToString();
    case Type::List:
      return ValueList().ToString();
    case Type::Map:
      return ValueMap().ToString();
    case Type::Path:
      return ValuePath().ToString();
    case Type::Point2d:
      return ValuePoint2d().ToString();
    case Type::Point3d:
      return ValuePoint3d().ToString();
    case Type::Enum:
      return ValueEnum().ToString();
    default:
      throw ValueException("Undefined behaviour");
  }
}

/* #endregion */

/* #region Record */
// Record:

inline Record::Record(mgp_result_record *record) : record_(record) {}

inline void Record::Insert(const char *field_name, bool value) {
  auto *mgp_val = mgp::MemHandlerCallback(value_make_bool, value);
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, std::int64_t value) {
  auto *mgp_val = mgp::MemHandlerCallback(value_make_int, value);
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, double value) {
  auto *mgp_val = mgp::MemHandlerCallback(value_make_double, value);
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, std::string_view value) {
  auto *mgp_val = mgp::MemHandlerCallback(value_make_string, value.data());
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const char *value) {
  auto *mgp_val = mgp::MemHandlerCallback(value_make_string, value);
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const List &list) {
  auto *mgp_val = mgp::value_make_list(mgp::MemHandlerCallback(list_copy, list.ptr_));
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const Map &map) {
  auto *mgp_val = mgp::value_make_map(mgp::MemHandlerCallback(map_copy, map.ptr_));
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const Node &node) {
  auto *mgp_val = mgp::value_make_vertex(mgp::MemHandlerCallback(vertex_copy, node.ptr_));
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const Relationship &relationship) {
  auto *mgp_val = mgp::value_make_edge(mgp::MemHandlerCallback(edge_copy, relationship.ptr_));
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const Path &path) {
  auto *mgp_val = mgp::value_make_path(mgp::MemHandlerCallback(path_copy, path.ptr_));
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const Date &date) {
  auto *mgp_val = mgp::value_make_date(mgp::MemHandlerCallback(date_copy, date.ptr_));
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const LocalTime &local_time) {
  auto *mgp_val = mgp::value_make_local_time(mgp::MemHandlerCallback(local_time_copy, local_time.ptr_));
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const LocalDateTime &local_date_time) {
  auto *mgp_val = mgp::value_make_local_date_time(mgp::MemHandlerCallback(local_date_time_copy, local_date_time.ptr_));
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const Duration &duration) {
  auto *mgp_val = mgp::value_make_duration(mgp::MemHandlerCallback(duration_copy, duration.ptr_));
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const ZonedDateTime &zoned_date_time) {
  auto *mgp_val = mgp::value_make_zoned_date_time(mgp::MemHandlerCallback(zoned_date_time_copy, zoned_date_time.ptr_));
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const Point2d &point) {
  auto *mgp_val = mgp::value_make_point_2d(mgp::MemHandlerCallback(point_2d_copy, point.ptr_));
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const Point3d &point) {
  auto *mgp_val = mgp::value_make_point_3d(mgp::MemHandlerCallback(point_3d_copy, point.ptr_));
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const Enum &enum_v) {
  auto *mgp_val = mgp::value_make_enum(mgp::MemHandlerCallback(enum_copy, enum_v.ptr_));
  {
    mgp::result_record_insert(record_, field_name, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Record::Insert(const char *field_name, const Value &value) {
  switch (value.Type()) {
    case Type::Bool:
      return Insert(field_name, value.ValueBool());
    case Type::Int:
      return Insert(field_name, value.ValueInt());
    case Type::Double:
      return Insert(field_name, value.ValueDouble());
    case Type::String:
      return Insert(field_name, value.ValueString());
    case Type::List:
      return Insert(field_name, value.ValueList());
    case Type::Map:
      return Insert(field_name, value.ValueMap());
    case Type::Node:
      return Insert(field_name, value.ValueNode());
    case Type::Relationship:
      return Insert(field_name, value.ValueRelationship());
    case Type::Path:
      return Insert(field_name, value.ValuePath());
    case Type::Date:
      return Insert(field_name, value.ValueDate());
    case Type::LocalTime:
      return Insert(field_name, value.ValueLocalTime());
    case Type::LocalDateTime:
      return Insert(field_name, value.ValueLocalDateTime());
    case Type::Duration:
      return Insert(field_name, value.ValueDuration());
    case Type::ZonedDateTime:
      return Insert(field_name, value.ValueZonedDateTime());
    case Type::Point2d:
      return Insert(field_name, value.ValuePoint2d());
    case Type::Point3d:
      return Insert(field_name, value.ValuePoint3d());
    case Type::Enum:
      return Insert(field_name, value.ValueEnum());

    default:
      throw ValueException("No Record.Insert for this datatype");
  }
}

// RecordFactory:

inline RecordFactory::RecordFactory(mgp_result *result) : result_(result) {}

inline Record RecordFactory::NewRecord() const {
  auto *record = mgp::result_new_record(result_);
  if (record == nullptr) {
    throw mg_exception::NotEnoughMemoryException();
  }
  return Record(record);
}

inline void RecordFactory::SetErrorMessage(const std::string_view error_msg) const {
  mgp::result_set_error_msg(result_, error_msg.data());
}

inline void RecordFactory::SetErrorMessage(const char *error_msg) const {
  mgp::result_set_error_msg(result_, error_msg);
}

// Result:

inline Result::Result(mgp_func_result *result) : result_(result) {}

inline void Result::SetValue() {
  auto *mgp_val = mgp::MemHandlerCallback(value_make_null);
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(bool value) {
  auto *mgp_val = mgp::MemHandlerCallback(value_make_bool, value);
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(std::int64_t value) {
  auto *mgp_val = mgp::MemHandlerCallback(value_make_int, value);
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(double value) {
  auto *mgp_val = mgp::MemHandlerCallback(value_make_double, value);
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(std::string_view value) {
  auto *mgp_val = mgp::MemHandlerCallback(value_make_string, value.data());
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(const char *value) {
  auto *mgp_val = mgp::MemHandlerCallback(value_make_string, value);
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(const List &list) {
  auto *mgp_val = mgp::value_make_list(mgp::MemHandlerCallback(list_copy, list.ptr_));
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(List &&list) {
  auto *mgp_val = mgp::value_make_list(list.ptr_);
  mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  mgp::value_destroy(mgp_val);
  list.ptr_ = nullptr;
}

inline void Result::SetValue(const Map &map) {
  auto *mgp_val = mgp::value_make_map(mgp::MemHandlerCallback(map_copy, map.ptr_));
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(Map &&map) {
  auto *mgp_val = mgp::value_make_map(map.ptr_);
  mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  mgp::value_destroy(mgp_val);
  map.ptr_ = nullptr;
}

inline void Result::SetValue(const Node &node) {
  auto *mgp_val = mgp::value_make_vertex(mgp::MemHandlerCallback(vertex_copy, node.ptr_));
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(const Relationship &relationship) {
  auto *mgp_val = mgp::value_make_edge(mgp::MemHandlerCallback(edge_copy, relationship.ptr_));
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(const Path &path) {
  auto *mgp_val = mgp::value_make_path(mgp::MemHandlerCallback(path_copy, path.ptr_));
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(const Date &date) {
  auto *mgp_val = mgp::value_make_date(mgp::MemHandlerCallback(date_copy, date.ptr_));
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(const LocalTime &local_time) {
  auto *mgp_val = mgp::value_make_local_time(mgp::MemHandlerCallback(local_time_copy, local_time.ptr_));
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(const LocalDateTime &local_date_time) {
  auto *mgp_val = mgp::value_make_local_date_time(mgp::MemHandlerCallback(local_date_time_copy, local_date_time.ptr_));
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(const Duration &duration) {
  auto *mgp_val = mgp::value_make_duration(mgp::MemHandlerCallback(duration_copy, duration.ptr_));
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(const ZonedDateTime &zoned_date_time) {
  auto *mgp_val = mgp::value_make_zoned_date_time(mgp::MemHandlerCallback(zoned_date_time_copy, zoned_date_time.ptr_));
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(const Point2d &point) {
  auto *mgp_val = mgp::value_make_point_2d(mgp::MemHandlerCallback(point_2d_copy, point.ptr_));
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(const Point3d &point) {
  auto *mgp_val = mgp::value_make_point_3d(mgp::MemHandlerCallback(point_3d_copy, point.ptr_));
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(const Enum &enum_v) {
  auto *mgp_val = mgp::value_make_enum(mgp::MemHandlerCallback(enum_copy, enum_v.ptr_));
  {
    mgp::MemHandlerCallback(func_result_set_value, result_, mgp_val);
  }
  mgp::value_destroy(mgp_val);
}

inline void Result::SetValue(const Value &value) {
  // func_result_set_value copies the value, so passing the wrapped pointer directly is safe.
  mgp::MemHandlerCallback(func_result_set_value, result_, value.ptr());
}

inline void Result::SetErrorMessage(const std::string_view error_msg) const {
  mgp::MemHandlerCallback(func_result_set_error_msg, result_, error_msg.data());
}

inline void Result::SetErrorMessage(const char *error_msg) const {
  mgp::MemHandlerCallback(func_result_set_error_msg, result_, error_msg);
}

/* #endregion */

/* #region Module */

// Parameter:

inline Parameter::Parameter(std::string_view name, Type type) : name(name), type_(type), list_item_type_(Type::Any) {}

inline Parameter::Parameter(std::string_view name, Type type, bool default_value)
    : name(name), type_(type), list_item_type_(Type::Any), optional(true), default_value(Value(default_value)) {}

inline Parameter::Parameter(std::string_view name, Type type, int64_t default_value)
    : name(name), type_(type), list_item_type_(Type::Any), optional(true), default_value(Value(default_value)) {}

inline Parameter::Parameter(std::string_view name, Type type, double default_value)
    : name(name), type_(type), list_item_type_(Type::Any), optional(true), default_value(Value(default_value)) {}

inline Parameter::Parameter(std::string_view name, Type type, std::string_view default_value)
    : name(name), type_(type), list_item_type_(Type::Any), optional(true), default_value(Value(default_value)) {}

inline Parameter::Parameter(std::string_view name, Type type, const char *default_value)
    : name(name), type_(type), list_item_type_(Type::Any), optional(true), default_value(Value(default_value)) {}

inline Parameter::Parameter(std::string_view name, Type type, Value default_value)
    : name(name), type_(type), list_item_type_(Type::Any), optional(true), default_value(std::move(default_value)) {}

inline Parameter::Parameter(std::string_view name, std::pair<Type, Type> list_type)
    : name(name), type_(list_type.first), list_item_type_(list_type.second) {}

inline Parameter::Parameter(std::string_view name, std::pair<Type, Type> list_type, Value default_value)
    : name(name),
      type_(list_type.first),
      list_item_type_(list_type.second),
      optional(true),
      default_value(std::move(default_value)) {}

inline mgp_type *Parameter::GetMGPType() const {
  if (type_ == Type::List) {
    return mgp::type_list(util::ToMGPType(list_item_type_));
  }

  return util::ToMGPType(type_);
}

// Return:

inline Return::Return(std::string_view name, Type type) : name(name), type_(type), list_item_type_(Type::Any) {}

inline Return::Return(std::string_view name, std::pair<Type, Type> list_type)
    : name(name), type_(list_type.first), list_item_type_(list_type.second) {}

inline mgp_type *Return::GetMGPType() const {
  if (type_ == Type::List) {
    return mgp::type_list(util::ToMGPType(list_item_type_));
  }

  return util::ToMGPType(type_);
}

inline ExecutionHeaders::ExecutionHeaders(mgp_execution_headers *headers) : headers_(headers) {}

inline size_t ExecutionHeaders::Size() const { return mgp::execution_headers_size(headers_); }

inline std::string ExecutionHeaders::At(size_t index) const {
  return std::string(mgp::execution_headers_at(headers_, index));
}

inline QueryExecution::QueryExecution(mgp_graph *graph) : graph_(graph) {}

inline ExecutionResult QueryExecution::ExecuteQuery(std::string_view query, mgp::Map params) const {
  return ExecuteQuery(std::string(query), params);
}

inline ExecutionResult QueryExecution::ExecuteQuery(std::string query, mgp::Map params) const {
  return ExecutionResult(mgp::MemHandlerCallback(execute_query, graph_, query.data(), params.ptr_), graph_);
}

inline ExecutionResult::ExecutionResult(mgp_execution_result *result, mgp_graph *graph)
    : result_(result), graph_(graph) {}

inline ExecutionResult::~ExecutionResult() { mgp::execution_result_destroy(result_); }

inline ExecutionHeaders ExecutionResult::Headers() const { return mgp::fetch_execution_headers(result_); };

inline std::optional<ExecutionRow> ExecutionResult::PullOne() const {
  auto *value = mgp::MemHandlerCallback(pull_one, result_, graph_);
  if (!value) {
    return std::nullopt;
  }

  return ExecutionRow(value);
}

inline bool ExecutionHeaders::Iterator::operator==(const Iterator &other) const {
  return iterable_ == other.iterable_ && index_ == other.index_;
}

inline bool ExecutionHeaders::Iterator::operator!=(const Iterator &other) const { return !(*this == other); }

inline ExecutionHeaders::Iterator &ExecutionHeaders::Iterator::operator++() {
  index_++;
  return *this;
}

inline std::string_view ExecutionHeaders::Iterator::operator*() const { return (*iterable_)[index_]; }

inline ExecutionHeaders::Iterator::Iterator(const ExecutionHeaders *iterable, size_t index)
    : iterable_(iterable), index_(index) {}

inline std::string_view ExecutionHeaders::operator[](size_t index) const {
  return std::string_view(mgp::execution_headers_at(headers_, index));
}

inline ExecutionHeaders::Iterator ExecutionHeaders::begin() { return Iterator(this, 0); }

inline ExecutionHeaders::Iterator ExecutionHeaders::end() { return Iterator(this, Size()); }

inline ExecutionHeaders::Iterator ExecutionHeaders::cbegin() { return Iterator(this, 0); }

inline ExecutionHeaders::Iterator ExecutionHeaders::cend() { return Iterator(this, Size()); }

inline ExecutionRow::ExecutionRow(mgp_map *row) : row_(row) {}

inline size_t ExecutionRow::Size() const { return row_.Size(); }

inline bool ExecutionRow::Empty() const { return row_.Empty(); }

inline Value ExecutionRow::operator[](std::string_view key) const { return row_[key]; }

inline Value ExecutionRow::At(std::string_view key) const { return row_.At(key); }

inline bool ExecutionRow::KeyExists(std::string_view key) const { return row_.KeyExists(key); }

inline mgp::Map ExecutionRow::Values() const { return mgp::Map(row_); }

// do not enter
namespace detail {
inline void AddParamsReturnsToProc(mgp_proc *proc, std::vector<Parameter> &parameters,
                                   const std::vector<Return> &returns) {
  for (const auto &parameter : parameters) {
    const auto *parameter_name = parameter.name.data();
    if (!parameter.optional) {
      mgp::proc_add_arg(proc, parameter_name, parameter.GetMGPType());
    } else {
      mgp::proc_add_opt_arg(proc, parameter_name, parameter.GetMGPType(), parameter.default_value.ptr());
    }
  }

  for (const auto return_ : returns) {
    const auto *return_name = return_.name.data();
    mgp::proc_add_result(proc, return_name, return_.GetMGPType());
  }
}
}  // namespace detail

inline bool CreateLabelIndex(mgp_graph *memgraph_graph, const std::string_view label) {
  return create_label_index(memgraph_graph, label.data());
}

inline bool DropLabelIndex(mgp_graph *memgraph_graph, const std::string_view label) {
  return drop_label_index(memgraph_graph, label.data());
}

inline List ListAllLabelIndices(mgp_graph *memgraph_graph) {
  auto *label_indices = mgp::MemHandlerCallback(list_all_label_indices, memgraph_graph);
  if (label_indices == nullptr) {
    throw ValueException("Couldn't list all label indices");
  }
  return List(label_indices, StealType{});
}

inline bool CreateLabelPropertyIndex(mgp_graph *memgraph_graph, const std::string_view label,
                                     const std::string_view property) {
  return create_label_property_index(memgraph_graph, label.data(), property.data());
}

inline bool DropLabelPropertyIndex(mgp_graph *memgraph_graph, const std::string_view label,
                                   const std::string_view property) {
  return drop_label_property_index(memgraph_graph, label.data(), property.data());
}

inline List ListAllLabelPropertyIndices(mgp_graph *memgraph_graph) {
  auto *label_property_indices = mgp::MemHandlerCallback(list_all_label_property_indices, memgraph_graph);
  if (label_property_indices == nullptr) {
    throw ValueException("Couldn't list all label+property indices");
  }
  return List(label_property_indices, StealType{});
}

inline bool CreateVertexPropertyIndex(mgp_graph *memgraph_graph, const std::string_view property) {
  return create_vertex_property_index(memgraph_graph, property.data());
}

inline bool DropVertexPropertyIndex(mgp_graph *memgraph_graph, const std::string_view property) {
  return drop_vertex_property_index(memgraph_graph, property.data());
}

inline List ListAllVertexPropertyIndices(mgp_graph *memgraph_graph) {
  auto *indices = mgp::MemHandlerCallback(list_all_vertex_property_indices, memgraph_graph);
  if (indices == nullptr) {
    throw ValueException("Couldn't list all vertex-property indices");
  }
  return List(indices, StealType{});
}

inline constexpr std::string_view kErrorMsgKey = "error_msg";
inline constexpr std::string_view kSearchResultsKey = "search_results";
inline constexpr std::string_view kAggregationResultsKey = "aggregation_results";

inline List SearchTextIndex(mgp_graph *memgraph_graph, std::string_view index_name, std::string_view search_query,
                            text_search_mode search_mode, const TextSearchConfig &config) {
  auto results_or_error = Map(mgp::MemHandlerCallback(graph_search_text_index,
                                                      memgraph_graph,
                                                      index_name.data(),
                                                      search_query.data(),
                                                      search_mode,
                                                      config.limit,
                                                      config.fuzzy_distance,
                                                      config.fuzzy_prefix,
                                                      config.fuzzy_transpositions),
                              StealType{});
  if (results_or_error.KeyExists(kErrorMsgKey)) {
    if (!results_or_error.At(kErrorMsgKey).IsString()) {
      throw TextSearchException{"The error message is not a string!"};
    }
    throw TextSearchException(results_or_error.At(kErrorMsgKey).ValueString().data());
  }

  if (!results_or_error.KeyExists(kSearchResultsKey)) {
    throw TextSearchException{"Error in text search results processing occurred."};
  }

  if (!results_or_error.At(kSearchResultsKey).IsList()) {
    throw TextSearchException{"Text index search results have wrong type!"};
  }

  return results_or_error.At(kSearchResultsKey).ValueList();
}

inline List SearchTextEdgeIndex(mgp_graph *memgraph_graph, std::string_view index_name, std::string_view search_query,
                                text_search_mode search_mode, const TextSearchConfig &config) {
  auto results_or_error = Map(mgp::MemHandlerCallback(graph_search_text_edge_index,
                                                      memgraph_graph,
                                                      index_name.data(),
                                                      search_query.data(),
                                                      search_mode,
                                                      config.limit,
                                                      config.fuzzy_distance,
                                                      config.fuzzy_prefix,
                                                      config.fuzzy_transpositions),
                              StealType{});
  if (results_or_error.KeyExists(kErrorMsgKey)) {
    if (!results_or_error.At(kErrorMsgKey).IsString()) {
      throw TextSearchException{"The error message is not a string!"};
    }
    throw TextSearchException(results_or_error.At(kErrorMsgKey).ValueString().data());
  }

  if (!results_or_error.KeyExists(kSearchResultsKey)) {
    throw TextSearchException{"Error in text search results processing occurred."};
  }

  if (!results_or_error.At(kSearchResultsKey).IsList()) {
    throw TextSearchException{"Text index search results have wrong type!"};
  }

  return results_or_error.At(kSearchResultsKey).ValueList();
}

inline std::string AggregateOverTextIndex(mgp_graph *memgraph_graph, std::string_view index_name,
                                          std::string_view search_query, std::string_view aggregation_query) {
  auto results_or_error = Map(mgp::MemHandlerCallback(graph_aggregate_over_text_index,
                                                      memgraph_graph,
                                                      index_name.data(),
                                                      search_query.data(),
                                                      aggregation_query.data()),
                              StealType{});

  if (results_or_error.KeyExists(kErrorMsgKey)) {
    if (!results_or_error.At(kErrorMsgKey).IsString()) {
      throw TextSearchException{"The error message is not a string!"};
    }
    throw TextSearchException(results_or_error.At(kErrorMsgKey).ValueString().data());
  }

  if (!results_or_error.KeyExists(kAggregationResultsKey)) {
    throw TextSearchException{"Incomplete text index aggregation results!"};
  }

  if (!results_or_error.At(kAggregationResultsKey).IsString()) {
    throw TextSearchException{"Text index aggregation results have wrong type!"};
  }

  // results_or_error is temporary -> need to copy
  return std::string(results_or_error.At(kAggregationResultsKey).ValueString());
}

inline std::string AggregateOverTextEdgeIndex(mgp_graph *memgraph_graph, std::string_view index_name,
                                              std::string_view search_query, std::string_view aggregation_query) {
  auto results_or_error = Map(mgp::MemHandlerCallback(graph_aggregate_over_text_edge_index,
                                                      memgraph_graph,
                                                      index_name.data(),
                                                      search_query.data(),
                                                      aggregation_query.data()),
                              StealType{});

  if (results_or_error.KeyExists(kErrorMsgKey)) {
    if (!results_or_error.At(kErrorMsgKey).IsString()) {
      throw TextSearchException{"The error message is not a string!"};
    }
    throw TextSearchException(results_or_error.At(kErrorMsgKey).ValueString().data());
  }

  if (!results_or_error.KeyExists(kAggregationResultsKey)) {
    throw TextSearchException{"Incomplete text edge index aggregation results!"};
  }

  if (!results_or_error.At(kAggregationResultsKey).IsString()) {
    throw TextSearchException{"Text edge index aggregation results have wrong type!"};
  }
  return std::string(results_or_error.At(kAggregationResultsKey).ValueString());
}

inline List SearchVectorIndex(mgp_graph *memgraph_graph, std::string_view index_name, List &query_vector,
                              size_t result_size) {
  auto results_or_error =
      Map(mgp::MemHandlerCallback(
              graph_search_vector_index, memgraph_graph, index_name.data(), query_vector.GetPtr(), result_size),
          StealType{});
  if (results_or_error.KeyExists(kErrorMsgKey)) {
    if (!results_or_error.At(kErrorMsgKey).IsString()) {
      throw VectorSearchException{"The error message is not a string!"};
    }
    throw VectorSearchException(results_or_error.At(kErrorMsgKey).ValueString().data());
  }
  return results_or_error.At(kSearchResultsKey).ValueList();
}

inline List SearchVectorIndexOnEdges(mgp_graph *memgraph_graph, std::string_view index_name, List &query_vector,
                                     size_t result_size) {
  auto results_or_error = Map(
      mgp::MemHandlerCallback(
          graph_search_vector_index_on_edges, memgraph_graph, index_name.data(), query_vector.GetPtr(), result_size),
      StealType{});
  if (results_or_error.KeyExists(kErrorMsgKey)) {
    if (!results_or_error.At(kErrorMsgKey).IsString()) {
      throw VectorSearchException{"The error message is not a string!"};
    }
    throw VectorSearchException(results_or_error.At(kErrorMsgKey).ValueString().data());
  }
  return results_or_error.At(kSearchResultsKey).ValueList();
}

inline List GetVectorIndexInfo(mgp_graph *memgraph_graph) {
  auto results_or_error = Map(mgp::MemHandlerCallback(graph_show_index_info, memgraph_graph), StealType{});

  if (results_or_error.KeyExists(kErrorMsgKey)) {
    if (!results_or_error.At(kErrorMsgKey).IsString()) {
      throw VectorSearchException{"The error message is not a string!"};
    }
    throw VectorSearchException(results_or_error.At(kErrorMsgKey).ValueString().data());
  }

  if (!results_or_error.KeyExists(kSearchResultsKey)) {
    throw VectorSearchException{"Incomplete index info results!"};
  }

  if (!results_or_error.At(kSearchResultsKey).IsList()) {
    throw VectorSearchException{"Index info results have wrong type!"};
  }

  return results_or_error.At(kSearchResultsKey).ValueList();
}

inline bool CreateExistenceConstraint(mgp_graph *memgraph_graph, const std::string_view label,
                                      const std::string_view property) {
  return create_existence_constraint(memgraph_graph, label.data(), property.data());
}

inline bool DropExistenceConstraint(mgp_graph *memgraph_graph, const std::string_view label,
                                    const std::string_view property) {
  return drop_existence_constraint(memgraph_graph, label.data(), property.data());
}

inline List ListAllExistenceConstraints(mgp_graph *memgraph_graph) {
  auto *existence_constraints = mgp::MemHandlerCallback(list_all_existence_constraints, memgraph_graph);
  if (existence_constraints == nullptr) {
    throw ValueException("Couldn't list all existence_constraints");
  }
  return List(existence_constraints, StealType{});
}

inline bool CreateUniqueConstraint(mgp_graph *memgraph_graph, const std::string_view label, mgp_list *properties) {
  return create_unique_constraint(memgraph_graph, label.data(), properties);
}

inline bool DropUniqueConstraint(mgp_graph *memgraph_graph, const std::string_view label, mgp_list *properties) {
  return drop_unique_constraint(memgraph_graph, label.data(), properties);
}

inline List ListAllUniqueConstraints(mgp_graph *memgraph_graph) {
  auto *unique_constraints = mgp::MemHandlerCallback(list_all_unique_constraints, memgraph_graph);
  if (unique_constraints == nullptr) {
    throw ValueException("Couldn't list all unique_constraints");
  }
  return List(unique_constraints, StealType{});
}

void AddProcedure(mgp_proc_cb callback, std::string_view name, ProcedureType proc_type,
                  std::vector<Parameter> parameters, std::vector<Return> returns, mgp_module *module,
                  mgp_memory * /*memory*/) {
  auto *proc = (proc_type == ProcedureType::Read) ? mgp::module_add_read_procedure(module, name.data(), callback)
                                                  : mgp::module_add_write_procedure(module, name.data(), callback);
  detail::AddParamsReturnsToProc(proc, parameters, returns);
}

void AddBatchProcedure(mgp_proc_cb callback, mgp_proc_initializer initializer, mgp_proc_cleanup cleanup,
                       std::string_view name, ProcedureType proc_type, std::vector<Parameter> parameters,
                       std::vector<Return> returns, mgp_module *module, mgp_memory * /*memory*/) {
  auto *proc = (proc_type == ProcedureType::Read)
                   ? mgp::module_add_batch_read_procedure(module, name.data(), callback, initializer, cleanup)
                   : mgp::module_add_batch_write_procedure(module, name.data(), callback, initializer, cleanup);
  detail::AddParamsReturnsToProc(proc, parameters, returns);
}

void AddFunction(mgp_func_cb callback, std::string_view name, std::vector<Parameter> parameters, mgp_module *module,
                 mgp_memory *memory) {
  auto *func = mgp::module_add_function(module, name.data(), callback);

  for (const auto &parameter : parameters) {
    const auto *parameter_name = parameter.name.data();

    if (!parameter.optional) {
      mgp::func_add_arg(func, parameter_name, parameter.GetMGPType());
    } else {
      mgp::func_add_opt_arg(func, parameter_name, parameter.GetMGPType(), parameter.default_value.ptr());
    }
  }
}

/* #endregion */

}  // namespace mgp

namespace std {
template <>
struct hash<mgp::Id> {
  size_t operator()(const mgp::Id &x) const { return hash<int64_t>()(x.AsInt()); };
};

template <>
struct hash<mgp::Node> {
  size_t operator()(const mgp::Node &x) const { return hash<int64_t>()(x.Id().AsInt()); };
};

template <>
struct hash<mgp::Relationship> {
  size_t operator()(const mgp::Relationship &x) const { return hash<int64_t>()(x.Id().AsInt()); };
};

template <>
struct hash<mgp::Path> {
  size_t operator()(const mgp::Path &x) const {
    // https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
    // See mgp::util::FnvCollection
    constexpr const uint64_t fnv_prime = 1099511628211U;
    uint64_t hash = 14695981039346656037U;

    auto multiply_and_xor = [](uint64_t &hash, size_t element_hash) {
      hash *= fnv_prime;
      hash ^= element_hash;
    };

    for (size_t i = 0; i < x.Length() - 1; ++i) {
      multiply_and_xor(hash, std::hash<mgp::Node>{}(x.GetNodeAt(i)));
      multiply_and_xor(hash, std::hash<mgp::Relationship>{}(x.GetRelationshipAt(i)));
    }
    multiply_and_xor(hash, std::hash<mgp::Node>{}(x.GetNodeAt(x.Length())));
    return hash;
  }
};

template <>
struct hash<mgp::Date> {
  size_t operator()(const mgp::Date &x) const { return hash<int64_t>()(x.Timestamp()); };
};

template <>
struct hash<mgp::LocalTime> {
  size_t operator()(const mgp::LocalTime &x) const { return hash<int64_t>()(x.Timestamp()); };
};

template <>
struct hash<mgp::LocalDateTime> {
  size_t operator()(const mgp::LocalDateTime &x) const { return hash<int64_t>()(x.Timestamp()); };
};

template <>
struct hash<mgp::Duration> {
  size_t operator()(const mgp::Duration &x) const { return hash<int64_t>()(x.Microseconds()); };
};

template <>
struct hash<mgp::ZonedDateTime> {
  size_t operator()(const mgp::ZonedDateTime &x) const {
    return mgp::util::HashCombine3<int64_t, std::string_view, int64_t>{}(x.Timestamp(), x.Timezone(), x.Offset());
  }
};

template <>
struct hash<mgp::MapItem> {
  // Defined below, once hash<mgp::Value> is complete.
  size_t operator()(const mgp::MapItem &x) const;
};

template <>
struct hash<mgp::Map> {
  size_t operator()(const mgp::Map &x) const {
    if (x.Size() == 0) {
      return 0;
    }
    // Combined commutatively: equal maps must hash alike whatever order their storage iterates in.
    size_t result = 0;
    for (const auto &item : x) {
      result ^= hash<mgp::MapItem>{}(item);
    }
    return result;
  }
};

template <>
struct hash<mgp::Point2d> {
  size_t operator()(const mgp::Point2d &x) const {
    size_t seed = 0;
    seed ^= std::hash<double>{}(x.X()) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    seed ^= std::hash<double>{}(x.Y()) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    seed ^= std::hash<uint16_t>{}(x.Srid()) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    return seed;
  }
};

template <>
struct hash<mgp::Point3d> {
  size_t operator()(const mgp::Point3d &x) const {
    size_t seed = 0;
    seed ^= std::hash<double>{}(x.X()) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    seed ^= std::hash<double>{}(x.Y()) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    seed ^= std::hash<double>{}(x.Z()) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    seed ^= std::hash<uint16_t>{}(x.Srid()) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    return seed;
  }
};

template <>
struct hash<mgp::Enum> {
  size_t operator()(const mgp::Enum &x) const {
    size_t seed = 0;
    seed ^= std::hash<std::string_view>{}(x.TypeName()) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    seed ^= std::hash<std::string_view>{}(x.ValueName()) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    return seed;
  }
};

template <>
struct hash<mgp::Value> {
  size_t operator()(const mgp::Value &x) const {
    switch (x.Type()) {
      case mgp::Type::Null:
        return 31;
      case mgp::Type::Any:
        throw mg_exception::InvalidArgumentException();
      case mgp::Type::Bool:
        return std::hash<bool>{}(x.ValueBool());
      case mgp::Type::Int:
        // we cast int to double for hashing purposes
        // to be consistent with equality (2.0 == 2) == true
        return std::hash<double>{}((double)x.ValueInt());
      case mgp::Type::Double:
        return std::hash<double>{}(x.ValueDouble());
      case mgp::Type::String:
        return std::hash<std::string_view>{}(x.ValueString());
      case mgp::Type::List:
        return mgp::util::FnvCollection<mgp::List, mgp::Value, std::hash<mgp::Value>>{}(x.ValueList());
      case mgp::Type::Map:
        return std::hash<mgp::Map>{}(x.ValueMap());
      case mgp::Type::Node:
        return std::hash<mgp::Node>{}(x.ValueNode());
      case mgp::Type::Relationship:
        return std::hash<mgp::Relationship>{}(x.ValueRelationship());
      case mgp::Type::Path:
        return std::hash<mgp::Path>{}(x.ValuePath());
      case mgp::Type::Date:
        return std::hash<mgp::Date>{}(x.ValueDate());
      case mgp::Type::LocalTime:
        return std::hash<mgp::LocalTime>{}(x.ValueLocalTime());
      case mgp::Type::LocalDateTime:
        return std::hash<mgp::LocalDateTime>{}(x.ValueLocalDateTime());
      case mgp::Type::Duration:
        return std::hash<mgp::Duration>{}(x.ValueDuration());
      case mgp::Type::ZonedDateTime:
        return std::hash<mgp::ZonedDateTime>{}(x.ValueZonedDateTime());
      case mgp::Type::Point2d:
        return std::hash<mgp::Point2d>{}(x.ValuePoint2d());
      case mgp::Type::Point3d:
        return std::hash<mgp::Point3d>{}(x.ValuePoint3d());
      case mgp::Type::Enum:
        return std::hash<mgp::Enum>{}(x.ValueEnum());
    }
    throw mg_exception::InvalidArgumentException();
  }
};

template <>
struct hash<mgp::List> {
  size_t operator()(const mgp::List &x) {
    return mgp::util::FnvCollection<mgp::List, mgp::Value, std::hash<mgp::Value>>{}(x);
  }
};

inline size_t hash<mgp::MapItem>::operator()(const mgp::MapItem &x) const {
  // The value is hashed too, or same-shaped maps all land in one bucket and dedupe goes quadratic.
  size_t seed = hash<std::string_view>{}(x.key);
  seed ^= hash<mgp::Value>{}(x.value) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
  return seed;
}
}  // namespace std