// https://github.com/kunitoki/LuaBridge3
// Copyright 2021, Lucio Asnaghi
// SPDX-License-Identifier: MIT

// clang-format off

#pragma once

#include <array>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <exception>
#include <functional>
#include <iostream>
#include <limits>
#include <list>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <string_view>
#include <system_error>
#include <tuple>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <variant>
#include <vector>

// Begin File: Source/LuaBridge/detail/Config.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2020, Dmitry Tarakanov
// Copyright 2019, George Tokmaji
// SPDX-License-Identifier: MIT

#if !(__cplusplus >= 201703L || (defined(_MSC_VER) && _HAS_CXX17))
#error LuaBridge 3 requires a compliant C++17 compiler, or C++17 has not been enabled !
#endif

#if defined(_MSC_VER)
#if _CPPUNWIND || _HAS_EXCEPTIONS
#define LUABRIDGE_HAS_EXCEPTIONS 1
#else
#define LUABRIDGE_HAS_EXCEPTIONS 0
#endif
#elif defined(__clang__)
#if __EXCEPTIONS && __has_feature(cxx_exceptions)
#define LUABRIDGE_HAS_EXCEPTIONS 1
#else
#define LUABRIDGE_HAS_EXCEPTIONS 0
#endif
#elif defined(__GNUC__)
#if defined(__cpp_exceptions) || defined(__EXCEPTIONS)
#define LUABRIDGE_HAS_EXCEPTIONS 1
#else
#define LUABRIDGE_HAS_EXCEPTIONS 0
#endif
#endif

#if defined(LUAU_FASTMATH_BEGIN)
#define LUABRIDGE_ON_LUAU 1
#elif defined(LUA_JROOT)
#define LUABRIDGE_ON_LUAJIT 1
#elif defined(LUA_VERSION_NUM)
#define LUABRIDGE_ON_LUA 1
#else
#error "Lua headers must be included prior to LuaBridge ones"
#endif

#if !defined(LUABRIDGE_SAFE_STACK_CHECKS)
#define LUABRIDGE_SAFE_STACK_CHECKS 1
#endif

// End File: Source/LuaBridge/detail/Config.h

// Begin File: Source/LuaBridge/detail/LuaHelpers.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// Copyright 2007, Nathan Reed
// SPDX-License-Identifier: MIT

namespace luabridge {

/**
 * @brief Helper for unused vars.
 */
template <class... Args>
constexpr void unused(Args&&...)
{
}

// These functions and defines are for Luau.
#if LUABRIDGE_ON_LUAU
inline int luaL_ref(lua_State* L, int idx)
{
    assert(idx == LUA_REGISTRYINDEX);

    const int ref = lua_ref(L, -1);

    lua_pop(L, 1);

    return ref;
}

inline void luaL_unref(lua_State* L, int idx, int ref)
{
    unused(idx);

    lua_unref(L, ref);
}

template <class T>
inline void* lua_newuserdata_x(lua_State* L, size_t sz)
{
    return lua_newuserdatadtor(L, sz, [](void* x)
    {
        T* object = static_cast<T*>(x);
        object->~T();
    });
}

inline void lua_pushcfunction_x(lua_State *L, lua_CFunction fn)
{
    lua_pushcfunction(L, fn, "");
}

inline void lua_pushcclosure_x(lua_State* L, lua_CFunction fn, int n)
{
    lua_pushcclosure(L, fn, "", n);
}

#else
using ::luaL_ref;
using ::luaL_unref;

template <class T>
inline void* lua_newuserdata_x(lua_State* L, size_t sz)
{
    return lua_newuserdata(L, sz);
}

inline void lua_pushcfunction_x(lua_State *L, lua_CFunction fn)
{
    lua_pushcfunction(L, fn);
}

inline void lua_pushcclosure_x(lua_State* L, lua_CFunction fn, int n)
{
    lua_pushcclosure(L, fn, n);
}

#endif // LUABRIDGE_ON_LUAU

// These are for Lua versions prior to 5.3.0.
#if LUA_VERSION_NUM < 503
inline lua_Number to_numberx(lua_State* L, int idx, int* isnum)
{
    lua_Number n = lua_tonumber(L, idx);

    if (isnum)
        *isnum = (n != 0 || lua_isnumber(L, idx));

    return n;
}

inline lua_Integer to_integerx(lua_State* L, int idx, int* isnum)
{
    int ok = 0;
    lua_Number n = to_numberx(L, idx, &ok);

    if (ok)
    {
        const auto int_n = static_cast<lua_Integer>(n);
        if (n == static_cast<lua_Number>(int_n))
        {
            if (isnum)
                *isnum = 1;
            
            return int_n;
        }
    }

    if (isnum)
        *isnum = 0;
    
    return 0;
}

#endif // LUA_VERSION_NUM < 503

// These are for Lua versions prior to 5.2.0.
#if LUA_VERSION_NUM < 502
using lua_Unsigned = std::make_unsigned_t<lua_Integer>;

#if ! LUABRIDGE_ON_LUAU
inline int lua_absindex(lua_State* L, int idx)
{
    if (idx > LUA_REGISTRYINDEX && idx < 0)
        return lua_gettop(L) + idx + 1;
    else
        return idx;
}
#endif

inline void lua_rawgetp(lua_State* L, int idx, const void* p)
{
    idx = lua_absindex(L, idx);
    luaL_checkstack(L, 1, "not enough stack slots");
    lua_pushlightuserdata(L, const_cast<void*>(p));
    lua_rawget(L, idx);
}

inline void lua_rawsetp(lua_State* L, int idx, const void* p)
{
    idx = lua_absindex(L, idx);
    luaL_checkstack(L, 1, "not enough stack slots");
    lua_pushlightuserdata(L, const_cast<void*>(p));
    lua_insert(L, -2);
    lua_rawset(L, idx);
}

#define LUA_OPEQ 1
#define LUA_OPLT 2
#define LUA_OPLE 3

inline int lua_compare(lua_State* L, int idx1, int idx2, int op)
{
    switch (op)
    {
    case LUA_OPEQ:
        return lua_equal(L, idx1, idx2);

    case LUA_OPLT:
        return lua_lessthan(L, idx1, idx2);

    case LUA_OPLE:
        return lua_equal(L, idx1, idx2) || lua_lessthan(L, idx1, idx2);

    default:
        return 0;
    }
}

inline int get_length(lua_State* L, int idx)
{
    return static_cast<int>(lua_objlen(L, idx));
}

#else // LUA_VERSION_NUM >= 502
inline int get_length(lua_State* L, int idx)
{
    lua_len(L, idx);
    const int len = static_cast<int>(luaL_checknumber(L, -1));
    lua_pop(L, 1);
    return len;
}

#endif // LUA_VERSION_NUM < 502

#ifndef LUA_OK
#define LUABRIDGE_LUA_OK 0
#else
#define LUABRIDGE_LUA_OK LUA_OK
#endif

/**
 * @brief Helper to throw or return an error code.
 */
template <class T, class ErrorType>
std::error_code throw_or_error_code(ErrorType error)
{
#if LUABRIDGE_HAS_EXCEPTIONS
    throw T(makeErrorCode(error).message().c_str());
#else
    return makeErrorCode(error);
#endif
}

template <class T, class ErrorType>
std::error_code throw_or_error_code(lua_State* L, ErrorType error)
{
#if LUABRIDGE_HAS_EXCEPTIONS
    throw T(L, makeErrorCode(error));
#else
    return unused(L), makeErrorCode(error);
#endif
}

/**
 * @brief Helper to throw or assert.
 */
template <class T, class... Args>
void throw_or_assert(Args&&... args)
{
#if LUABRIDGE_HAS_EXCEPTIONS
    throw T(std::forward<Args>(args)...);
#else
    unused(std::forward<Args>(args)...);
    assert(false);
#endif
}

/**
 * @brief Helper to set unsigned.
 */
template <class T>
void pushunsigned(lua_State* L, T value)
{
    static_assert(std::is_unsigned_v<T>);

    lua_pushinteger(L, static_cast<lua_Integer>(value));
}

/**
 * @brief Helper to convert to integer.
 */
inline lua_Number tonumber(lua_State* L, int idx, int* isnum)
{
#if ! LUABRIDGE_ON_LUAU && LUA_VERSION_NUM > 502
    return lua_tonumberx(L, idx, isnum);
#else
    return to_numberx(L, idx, isnum);
#endif
}

/**
 * @brief Helper to convert to integer.
 */
inline lua_Integer tointeger(lua_State* L, int idx, int* isnum)
{
#if ! LUABRIDGE_ON_LUAU && LUA_VERSION_NUM > 502
    return lua_tointegerx(L, idx, isnum);
#else
    return to_integerx(L, idx, isnum);
#endif
}

/**
 * @brief Get a table value, bypassing metamethods.
 */
inline void rawgetfield(lua_State* L, int index, char const* key)
{
    assert(lua_istable(L, index));
    index = lua_absindex(L, index);
    lua_pushstring(L, key);
    lua_rawget(L, index);
}

/**
 * @brief Set a table value, bypassing metamethods.
 */
inline void rawsetfield(lua_State* L, int index, char const* key)
{
    assert(lua_istable(L, index));
    index = lua_absindex(L, index);
    lua_pushstring(L, key);
    lua_insert(L, -2);
    lua_rawset(L, index);
}

/**
 * @brief Returns true if the value is a full userdata (not light).
 */
[[nodiscard]] inline bool isfulluserdata(lua_State* L, int index)
{
    return lua_isuserdata(L, index) && !lua_islightuserdata(L, index);
}

/**
 * @brief Test lua_State objects for global equality.
 *
 * This can determine if two different lua_State objects really point
 * to the same global state, such as when using coroutines.
 *
 * @note This is used for assertions.
 */
[[nodiscard]] inline bool equalstates(lua_State* L1, lua_State* L2)
{
    return lua_topointer(L1, LUA_REGISTRYINDEX) == lua_topointer(L2, LUA_REGISTRYINDEX);
}

/**
 * @brief Return an aligned pointer of type T.
 */
template <class T>
[[nodiscard]] T* align(void* ptr) noexcept
{
    const auto address = reinterpret_cast<size_t>(ptr);

    const auto offset = address % alignof(T);
    const auto aligned_address = (offset == 0) ? address : (address + alignof(T) - offset);

    return reinterpret_cast<T*>(aligned_address);
}

/**
 * @brief Return the space needed to align the type T on an unaligned address.
 */
template <class T>
[[nodiscard]] constexpr size_t maximum_space_needed_to_align() noexcept
{
    return sizeof(T) + alignof(T) - 1;
}

/**
 * @brief Deallocate lua userdata taking into account alignment.
 */
template <class T>
int lua_deleteuserdata_aligned(lua_State* L)
{
    assert(isfulluserdata(L, 1));

    T* aligned = align<T>(lua_touserdata(L, 1));
    aligned->~T();

    return 0;
}

/**
 * @brief Allocate lua userdata taking into account alignment.
 *
 * Using this instead of lua_newuserdata directly prevents alignment warnings on 64bits platforms.
 */
template <class T, class... Args>
void* lua_newuserdata_aligned(lua_State* L, Args&&... args)
{
#if LUABRIDGE_ON_LUAU
    void* pointer = lua_newuserdatadtor(L, maximum_space_needed_to_align<T>(), [](void* x)
    {
        T* aligned = align<T>(x);
        aligned->~T();
    });
#else
    void* pointer = lua_newuserdata_x<T>(L, maximum_space_needed_to_align<T>());

    lua_newtable(L);
    lua_pushcfunction_x(L, &lua_deleteuserdata_aligned<T>);
    rawsetfield(L, -2, "__gc");
    lua_setmetatable(L, -2);
#endif

    T* aligned = align<T>(pointer);

    new (aligned) T(std::forward<Args>(args)...);

    return pointer;
}

/**
 * @brief Checks if the value on the stack is a number type and can fit into the corresponding c++ integral type..
 */
template <class U = lua_Integer, class T>
constexpr bool is_integral_representable_by(T value)
{
    constexpr bool same_signedness = (std::is_unsigned_v<T> && std::is_unsigned_v<U>)
        || (!std::is_unsigned_v<T> && !std::is_unsigned_v<U>);

    if constexpr (sizeof(T) == sizeof(U))
    {
        if constexpr (same_signedness)
            return true;

        if constexpr (std::is_unsigned_v<T>)
            return value <= static_cast<T>(std::numeric_limits<U>::max());
        
        return value >= static_cast<T>(std::numeric_limits<U>::min())
            && static_cast<U>(value) <= std::numeric_limits<U>::max();
    }

    if constexpr (sizeof(T) < sizeof(U))
    {
        return static_cast<U>(value) >= std::numeric_limits<U>::min()
            && static_cast<U>(value) <= std::numeric_limits<U>::max();
    }

    if constexpr (std::is_unsigned_v<T>)
        return value <= static_cast<T>(std::numeric_limits<U>::max());

    return value >= static_cast<T>(std::numeric_limits<U>::min())
        && value <= static_cast<T>(std::numeric_limits<U>::max());
}

template <class U = lua_Integer>
bool is_integral_representable_by(lua_State* L, int index)
{
    int isValid = 0;

    const auto value = tointeger(L, index, &isValid);

    return isValid ? is_integral_representable_by<U>(value) : false;
}

/**
 * @brief Checks if the value on the stack is a number type and can fit into the corresponding c++ numerical type..
 */
template <class U = lua_Number, class T>
constexpr bool is_floating_point_representable_by(T value)
{
    if constexpr (sizeof(T) == sizeof(U))
        return true;

    if constexpr (sizeof(T) < sizeof(U))
        return static_cast<U>(value) >= -std::numeric_limits<U>::max()
            && static_cast<U>(value) <= std::numeric_limits<U>::max();

    return value >= static_cast<T>(-std::numeric_limits<U>::max())
        && value <= static_cast<T>(std::numeric_limits<U>::max());
}

template <class U = lua_Number>
bool is_floating_point_representable_by(lua_State* L, int index)
{
    int isValid = 0;

    const auto value = tonumber(L, index, &isValid);

    return isValid ? is_floating_point_representable_by<U>(value) : false;
}

} // namespace luabridge

// End File: Source/LuaBridge/detail/LuaHelpers.h

// Begin File: Source/LuaBridge/detail/Errors.h

// https://github.com/vinniefalco/LuaBridge
// Copyright 2021, Lucio Asnaghi
// SPDX-License-Identifier: MIT

namespace luabridge {

//=================================================================================================
namespace detail {

static inline constexpr char error_lua_stack_overflow[] = "stack overflow";

} // namespace detail

//=================================================================================================
/**
 * @brief LuaBridge error codes.
 */
enum class ErrorCode
{
    ClassNotRegistered = 1,

    LuaStackOverflow,

    LuaFunctionCallFailed,

    IntegerDoesntFitIntoLuaInteger,
    
    FloatingPointDoesntFitIntoLuaNumber,
};

//=================================================================================================
namespace detail {
struct ErrorCategory : std::error_category
{
    const char* name() const noexcept override
    {
        return "luabridge";
    }

    std::string message(int ev) const override
    {
        switch (static_cast<ErrorCode>(ev))
        {
        case ErrorCode::ClassNotRegistered:
            return "The class is not registered in LuaBridge";

        case ErrorCode::LuaStackOverflow:
            return "The lua stack has overflow";

        case ErrorCode::LuaFunctionCallFailed:
            return "The lua function invocation raised an error";

        case ErrorCode::IntegerDoesntFitIntoLuaInteger:
            return "The native integer can't fit inside a lua integer";

        case ErrorCode::FloatingPointDoesntFitIntoLuaNumber:
            return "The native floating point can't fit inside a lua number";
                
        default:
            return "Unknown error";
        }
    }

    static const ErrorCategory& getInstance() noexcept
    {
        static ErrorCategory category;
        return category;
    }
};
} // namespace detail

//=================================================================================================
/**
 * @brief Construct an error code from the error enum.
 */
inline std::error_code makeErrorCode(ErrorCode e)
{
  return { static_cast<int>(e), detail::ErrorCategory::getInstance() };
}

} // namespace luabridge

namespace std {
template <> struct is_error_code_enum<luabridge::ErrorCode> : true_type {};
} // namespace std

// End File: Source/LuaBridge/detail/Errors.h

// Begin File: Source/LuaBridge/detail/LuaException.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2021, Lucio Asnaghi
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// Copyright 2008, Nigel Atkinson <suprapilot+LuaCode@gmail.com>
// SPDX-License-Identifier: MIT

namespace luabridge {

//================================================================================================
class LuaException : public std::exception
{
public:
    //=============================================================================================
    /**
     * @brief Construct a LuaException after a lua_pcall().
     *
     * Assumes the error string is on top of the stack, but provides a generic error message otherwise.
     */
    LuaException(lua_State* L, std::error_code code)
        : m_L(L)
        , m_code(code)
    {
    }

    ~LuaException() noexcept override
    {
    }

    //=============================================================================================
    /**
     * @brief Return the error message.
     */
    const char* what() const noexcept override
    {
        return m_what.c_str();
    }

    //=============================================================================================
    /**
     * @brief Throw an exception or raises a luaerror when exceptions are disabled.
     *
     * This centralizes all the exceptions thrown, so that we can set breakpoints before the stack is
     * unwound, or otherwise customize the behavior.
     */
    template <class Exception>
    static void raise(const Exception& e)
    {
        assert(areExceptionsEnabled());

#if LUABRIDGE_HAS_EXCEPTIONS
        throw e;
#else
        unused(e);

        std::abort();
#endif
    }

    //=============================================================================================
    /**
     * @brief Check if exceptions are enabled.
     */
    static bool areExceptionsEnabled() noexcept
    {
        return exceptionsEnabled();
    }

    /**
     * @brief Initializes error handling.
     *
     * Subsequent Lua errors are translated to C++ exceptions, or logging and abort if exceptions are disabled.
     */
    static void enableExceptions(lua_State* L) noexcept
    {
        exceptionsEnabled() = true;

#if LUABRIDGE_ON_LUAU
        auto callbacks = lua_callbacks(L);
        callbacks->panic = +[](lua_State* L, int) { panicHandlerCallback(L); };
#else
        lua_atpanic(L, panicHandlerCallback);
#endif
    }

    //=============================================================================================
    /**
     * @brief Retrieve the lua_State associated with the exception.
     *
     * @return A Lua state.
     */
    lua_State* state() const { return m_L; }

private:
    struct FromLua {};

    LuaException(lua_State* L, std::error_code code, FromLua)
        : m_L(L)
        , m_code(code)
    {
        whatFromStack();
    }

    void whatFromStack()
    {
        std::stringstream ss;

        const char* errorText = nullptr;

        if (lua_gettop(m_L) > 0)
        {
            errorText = lua_tostring(m_L, -1);
            lua_pop(m_L, 1);
        }

        ss << (errorText ? errorText : "Unknown error") << " (code=" << m_code.message() << ")";

        m_what = std::move(ss).str();
    }

    static int panicHandlerCallback(lua_State* L)
    {
#if LUABRIDGE_HAS_EXCEPTIONS
        throw LuaException(L, makeErrorCode(ErrorCode::LuaFunctionCallFailed), FromLua{});
#else
        unused(L);

        std::abort();
#endif
    }

    static bool& exceptionsEnabled()
    {
        static bool areExceptionsEnabled = false;
        return areExceptionsEnabled;
    }

    lua_State* m_L = nullptr;
    std::error_code m_code;
    std::string m_what;
};

//=================================================================================================
/**
 * @brief Initializes error handling using C++ exceptions.
 *
 * Subsequent Lua errors are translated to C++ exceptions. It aborts the application if called when no exceptions.
 */
inline void enableExceptions(lua_State* L) noexcept
{
#if LUABRIDGE_HAS_EXCEPTIONS
    LuaException::enableExceptions(L);
#else
    unused(L);

    assert(false); // Never call this function when exceptions are not enabled.
#endif
}

} // namespace luabridge

// End File: Source/LuaBridge/detail/LuaException.h

// Begin File: Source/LuaBridge/detail/ClassInfo.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2020, Dmitry Tarakanov
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// SPDX-License-Identifier: MIT

namespace luabridge {
namespace detail {

//=================================================================================================
/**
 * @brief A unique key for a type name in a metatable.
 */
inline const void* getTypeKey() noexcept
{
    return reinterpret_cast<void*>(0x71);
}

//=================================================================================================
/**
 * @brief The key of a const table in another metatable.
 */
inline const void* getConstKey() noexcept
{
    return reinterpret_cast<void*>(0xc07);
}

//=================================================================================================
/**
 * @brief The key of a class table in another metatable.
 */
inline const void* getClassKey() noexcept
{
    return reinterpret_cast<void*>(0xc1a);
}

//=================================================================================================
/**
 * @brief The key of a propget table in another metatable.
 */
inline const void* getPropgetKey() noexcept
{
    return reinterpret_cast<void*>(0x6e7);
}

//=================================================================================================
/**
 * @brief The key of a propset table in another metatable.
 */
inline const void* getPropsetKey() noexcept
{
    return reinterpret_cast<void*>(0x5e7);
}

//=================================================================================================
/**
 * @brief The key of a static table in another metatable.
 */
inline const void* getStaticKey() noexcept
{
    return reinterpret_cast<void*>(0x57a);
}

//=================================================================================================
/**
 * @brief The key of a parent table in another metatable.
 */
inline const void* getParentKey() noexcept
{
    return reinterpret_cast<void*>(0xdad);
}

//=================================================================================================
/**
 * @brief Get the key for the static table in the Lua registry.
 *
 * The static table holds the static data members, static properties, and static member functions for a class.
 */
template <class T>
const void* getStaticRegistryKey() noexcept
{
    static char value;
    return std::addressof(value);
}

//=================================================================================================
/**
 * @brief Get the key for the class table in the Lua registry.
 *
 * The class table holds the data members, properties, and member functions of a class. Read-only data and properties, and const
 * member functions are also placed here (to save a lookup in the const table).
 */
template <class T>
const void* getClassRegistryKey() noexcept
{
    static char value;
    return std::addressof(value);
}

//=================================================================================================
/**
 * @brief Get the key for the const table in the Lua registry.
 *
 * The const table holds read-only data members and properties, and const member functions of a class.
 */
template <class T>
const void* getConstRegistryKey() noexcept
{
    static char value;
    return std::addressof(value);
}

} // namespace detail
} // namespace luabridge

// End File: Source/LuaBridge/detail/ClassInfo.h

// Begin File: Source/LuaBridge/detail/TypeTraits.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2019, Dmitry Tarakanov
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// SPDX-License-Identifier: MIT

namespace luabridge {

//=================================================================================================
/**
 * @brief Container traits.
 *
 * Unspecialized ContainerTraits has the isNotContainer typedef for SFINAE. All user defined containers must supply an appropriate
 * specialization for ContinerTraits (without the alias isNotContainer). The containers that come with LuaBridge also come with the
 * appropriate ContainerTraits specialization.
 *
 * @note See the corresponding declaration for details.
 *
 * A specialization of ContainerTraits for some generic type ContainerType looks like this:
 *
 * @code
 *
 *  template <class T>
 *  struct ContainerTraits<ContainerType<T>>
 *  {
 *    using Type = T;
 *
 *    static ContainerType<T> construct(T* c)
 *    {
 *      return c; // Implementation-dependent on ContainerType
 *    }
 *
 *    static T* get(const ContainerType<T>& c)
 *    {
 *      return c.get(); // Implementation-dependent on ContainerType
 *    }
 *  };
 *
 * @endcode
 */
template <class T>
struct ContainerTraits
{
    using IsNotContainer = bool;

    using Type = T;
};

/**
 * @brief Register shared_ptr support as container.
 *
 * @tparam T Class that is hold by the shared_ptr, must inherit from std::enable_shared_from_this.
 */
template <class T>
struct ContainerTraits<std::shared_ptr<T>>
{
    static_assert(std::is_base_of_v<std::enable_shared_from_this<T>, T>);
    
    using Type = T;

    static std::shared_ptr<T> construct(T* t)
    {
        return t->shared_from_this();
    }

    static T* get(const std::shared_ptr<T>& c)
    {
        return c.get();
    }
};

namespace detail {

//=================================================================================================
/**
 * @brief Determine if type T is a container.
 *
 * To be considered a container, there must be a specialization of ContainerTraits with the required fields.
 */
template <class T>
class IsContainer
{
private:
    typedef char yes[1]; // sizeof (yes) == 1
    typedef char no[2]; // sizeof (no)  == 2

    template <class C>
    static constexpr no& test(typename C::IsNotContainer*);

    template <class>
    static constexpr yes& test(...);

public:
    static constexpr bool value = sizeof(test<ContainerTraits<T>>(0)) == sizeof(yes);
};

} // namespace detail
} // namespace luabridge

// End File: Source/LuaBridge/detail/TypeTraits.h

// Begin File: Source/LuaBridge/detail/Userdata.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2019, Dmitry Tarakanov
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// SPDX-License-Identifier: MIT

namespace luabridge {
namespace detail {

//=================================================================================================
/**
 * @brief Return the identity pointer for our lightuserdata tokens.
 *
 * Because of Lua's dynamic typing and our improvised system of imposing C++ class structure, there is the possibility that executing
 * scripts may knowingly or unknowingly cause invalid data to get passed to the C functions created by LuaBridge.
 *
 * In particular, our security model addresses the following:
 *
 *   1. Scripts cannot create a userdata (ignoring the debug lib).
 *
 *   2. Scripts cannot create a lightuserdata (ignoring the debug lib).
 *
 *   3. Scripts cannot set the metatable on a userdata.
 */

/**
 * @brief Interface to a class pointer retrievable from a userdata.
 */
class Userdata
{
private:
    //=============================================================================================
    /**
     * @brief Validate and retrieve a Userdata on the stack.
     *
     * The Userdata must exactly match the corresponding class table or const table, or else a Lua error is raised. This is used for the
     * __gc metamethod.
     */
    static Userdata* getExactClass(lua_State* L, int index, const void* classKey)
    {
        return (void)classKey, static_cast<Userdata*>(lua_touserdata(L, lua_absindex(L, index)));
    }

    //=============================================================================================
    /**
     * @brief Validate and retrieve a Userdata on the stack.
     *
     * The Userdata must be derived from or the same as the given base class, identified by the key. If canBeConst is false, generates
     * an error if the resulting Userdata represents to a const object. We do the type check first so that the error message is informative.
     */
    static Userdata* getClass(lua_State* L,
                              int index,
                              const void* registryConstKey,
                              const void* registryClassKey,
                              bool canBeConst)
    {
        index = lua_absindex(L, index);

        lua_getmetatable(L, index); // Stack: object metatable (ot) | nil
        if (!lua_istable(L, -1))
        {
            lua_rawgetp(L, LUA_REGISTRYINDEX, registryClassKey); // Stack: registry metatable (rt) | nil
            return throwBadArg(L, index);
        }

        lua_rawgetp(L, -1, getConstKey()); // Stack: ot | nil, const table (co) | nil
        assert(lua_istable(L, -1) || lua_isnil(L, -1));

        // If const table is NOT present, object is const. Use non-const registry table
        // if object cannot be const, so constness validation is done automatically.
        // E.g. nonConstFn (constObj)
        // -> canBeConst = false, isConst = true
        // -> 'Class' registry table, 'const Class' object table
        // -> 'expected Class, got const Class'
        bool isConst = lua_isnil(L, -1); // Stack: ot | nil, nil, rt
        if (isConst && canBeConst)
        {
            lua_rawgetp(L, LUA_REGISTRYINDEX, registryConstKey); // Stack: ot, nil, rt
        }
        else
        {
            lua_rawgetp(L, LUA_REGISTRYINDEX, registryClassKey); // Stack: ot, co, rt
        }

        lua_insert(L, -3); // Stack: rt, ot, co | nil
        lua_pop(L, 1); // Stack: rt, ot

        for (;;)
        {
            if (lua_rawequal(L, -1, -2)) // Stack: rt, ot
            {
                lua_pop(L, 2); // Stack: -
                return static_cast<Userdata*>(lua_touserdata(L, index));
            }

            // Replace current metatable with it's base class.
            lua_rawgetp(L, -1, getParentKey()); // Stack: rt, ot, parent ot (pot) | nil

            if (lua_isnil(L, -1)) // Stack: rt, ot, nil
            {
                // Drop the object metatable because it may be some parent metatable
                lua_pop(L, 2); // Stack: rt
                return throwBadArg(L, index);
            }

            lua_remove(L, -2); // Stack: rt, pot
        }

        // no return
    }

    static bool isInstance(lua_State* L, int index, const void* registryClassKey)
    {
        index = lua_absindex(L, index);

        int result = lua_getmetatable(L, index); // Stack: object metatable (ot) | nothing
        if (result == 0)
            return false; // Nothing was pushed on the stack

        if (!lua_istable(L, -1))
        {
            lua_pop(L, 1); // Stack: -
            return false;
        }

        lua_rawgetp(L, LUA_REGISTRYINDEX, registryClassKey); // Stack: ot, rt
        lua_insert(L, -2); // Stack: rt, ot

        for (;;)
        {
            if (lua_rawequal(L, -1, -2)) // Stack: rt, ot
            {
                lua_pop(L, 2); // Stack: -
                return true;
            }

            // Replace current metatable with it's base class.
            lua_rawgetp(L, -1, getParentKey()); // Stack: rt, ot, parent ot (pot) | nil

            if (lua_isnil(L, -1)) // Stack: rt, ot, nil
            {
                lua_pop(L, 3); // Stack: -
                return false;
            }

            lua_remove(L, -2); // Stack: rt, pot
        }
    }

    static Userdata* throwBadArg(lua_State* L, int index)
    {
        assert(lua_istable(L, -1) || lua_isnil(L, -1)); // Stack: rt | nil

        const char* expected = 0;
        if (lua_isnil(L, -1)) // Stack: nil
        {
            expected = "unregistered class";
        }
        else
        {
            lua_rawgetp(L, -1, getTypeKey()); // Stack: rt, registry type
            expected = lua_tostring(L, -1);
        }

        const char* got = 0;
        if (lua_isuserdata(L, index))
        {
            lua_getmetatable(L, index); // Stack: ..., ot | nil
            if (lua_istable(L, -1)) // Stack: ..., ot
            {
                lua_rawgetp(L, -1, getTypeKey()); // Stack: ..., ot, object type | nil
                if (lua_isstring(L, -1))
                {
                    got = lua_tostring(L, -1);
                }
            }
        }

        if (!got)
        {
            got = lua_typename(L, lua_type(L, index));
        }

        luaL_argerror(L, index, lua_pushfstring(L, "%s expected, got %s", expected, got));
        return nullptr;
    }

public:
    virtual ~Userdata() {}

    //=============================================================================================
    /**
     * @brief Returns the Userdata* if the class on the Lua stack matches.
     *
     * If the class does not match, a Lua error is raised.
     *
     * @tparam T  A registered user class.
     *
     * @param L A Lua state.
     * @param index The index of an item on the Lua stack.
     *
     * @return A userdata pointer if the class matches.
     */
    template <class T>
    static Userdata* getExact(lua_State* L, int index)
    {
        return getExactClass(L, index, detail::getClassRegistryKey<T>());
    }

    //=============================================================================================
    /**
     * @brief Get a pointer to the class from the Lua stack.
     *
     * If the object is not the class or a subclass, or it violates the const-ness, a Lua error is raised.
     *
     * @tparam T A registered user class.
     *
     * @param L A Lua state.
     * @param index The index of an item on the Lua stack.
     * @param canBeConst TBD
     *
     * @return A pointer if the class and constness match.
     */
    template <class T>
    static T* get(lua_State* L, int index, bool canBeConst)
    {
        if (lua_isnil(L, index))
            return nullptr;

        return static_cast<T*>(getClass(L,
                                        index,
                                        detail::getConstRegistryKey<T>(),
                                        detail::getClassRegistryKey<T>(),
                                        canBeConst)
                                   ->getPointer());
    }

    template <class T>
    static bool isInstance(lua_State* L, int index)
    {
        return isInstance(L, index, detail::getClassRegistryKey<T>());
    }

protected:
    Userdata() = default;

    /**
     * @brief Get an untyped pointer to the contained class.
     */
    void* getPointer() const noexcept
    {
        return m_p;
    }

    void* m_p = nullptr; // subclasses must set this
};

//=================================================================================================
/**
 * @brief Wraps a class object stored in a Lua userdata.
 *
 * The lifetime of the object is managed by Lua. The object is constructed inside the userdata using placement new.
 */
template <class T>
class UserdataValue : public Userdata
{
public:
    UserdataValue(const UserdataValue&) = delete;
    UserdataValue operator=(const UserdataValue&) = delete;

    ~UserdataValue()
    {
        if (getPointer() != nullptr)
        {
            getObject()->~T();
        }
    }

    /**
     * @brief Push a T via placement new.
     *
     * The caller is responsible for calling placement new using the returned uninitialized storage.
     *
     * @param L A Lua state.
     *
     * @return An object referring to the newly created userdata value.
     */
    static UserdataValue<T>* place(lua_State* L, std::error_code& ec)
    {
        auto* ud = new (lua_newuserdata_x<UserdataValue<T>>(L, sizeof(UserdataValue<T>))) UserdataValue<T>();

        lua_rawgetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey<T>());

        if (!lua_istable(L, -1))
        {
            lua_pop(L, 1); // possibly: a nil

            ud->~UserdataValue<T>();

            ec = throw_or_error_code<LuaException>(L, ErrorCode::ClassNotRegistered);

            return nullptr;
        }

        lua_setmetatable(L, -2);

        return ud;
    }

    /**
     * @brief Push T via copy construction from U.
     *
     * @tparam U A container type.
     *
     * @param L A Lua state.
     * @param u A container object l-value reference.
     * @param ec Error code that will be set in case of failure to push on the lua stack.
     */
    template <class U>
    static bool push(lua_State* L, const U& u, std::error_code& ec)
    {
        auto* ud = place(L, ec);

        if (!ud)
            return false;

        new (ud->getObject()) U(u);

        ud->commit();

        return true;
    }

    /**
     * @brief Push T via move construction from U.
     *
     * @tparam U A container type.
     *
     * @param L A Lua state.
     * @param u A container object r-value reference.
     * @param ec Error code that will be set in case of failure to push on the lua stack.
     */
    template <class U>
    static bool push(lua_State* L, U&& u, std::error_code& ec)
    {
        auto* ud = place(L, ec);

        if (!ud)
            return false;

        new (ud->getObject()) U(std::move(u));

        ud->commit();

        return true;
    }

    /**
     * @brief Confirm object construction.
     */
    void commit() noexcept
    {
        m_p = getObject();
    }

    T* getObject() noexcept
    {
        // If this fails to compile it means you forgot to provide
        // a Container specialization for your container!
        return reinterpret_cast<T*>(&m_storage);
    }

private:
    /**
     * @brief Used for placement construction.
     */
    UserdataValue() noexcept
        : Userdata()
    {
    }

    std::aligned_storage_t<sizeof(T), alignof(T)> m_storage;
};

//=================================================================================================
/**
 * @brief Wraps a pointer to a class object inside a Lua userdata.
 *
 * The lifetime of the object is managed by C++.
 */
class UserdataPtr : public Userdata
{
public:
    UserdataPtr(const UserdataPtr&) = delete;
    UserdataPtr operator=(const UserdataPtr&) = delete;

    /**
     * @brief Push non-const pointer to object.
     *
     * @tparam T A user registered class.
     *
     * @param L A Lua state.
     * @param p A pointer to the user class instance.
     * @param ec Error code that will be set in case of failure to push on the lua stack.
     */
    template <class T>
    static bool push(lua_State* L, T* p, std::error_code& ec)
    {
        if (p)
            return push(L, p, getClassRegistryKey<T>(), ec);

        lua_pushnil(L);
        return true;
    }

    /**
     * @brief Push const pointer to object.
     *
     * @tparam T A user registered class.
     *
     * @param L A Lua state.
     * @param p A pointer to the user class instance.
     * @param ec Error code that will be set in case of failure to push on the lua stack.
     */
    template <class T>
    static bool push(lua_State* L, const T* p, std::error_code& ec)
    {
        if (p)
            return push(L, p, getConstRegistryKey<T>(), ec);

        lua_pushnil(L);
        return true;
    }

private:
    /**
     * @brief Push a pointer to object using metatable key.
     */
    static bool push(lua_State* L, const void* p, const void* key, std::error_code& ec)
    {
        auto* ptr = new (lua_newuserdata_x<UserdataPtr>(L, sizeof(UserdataPtr))) UserdataPtr(const_cast<void*>(p));

        lua_rawgetp(L, LUA_REGISTRYINDEX, key);

        if (!lua_istable(L, -1))
        {
            lua_pop(L, 1); // possibly: a nil

            ptr->~UserdataPtr();

            ec = throw_or_error_code<LuaException>(L, ErrorCode::ClassNotRegistered);

            return false;
        }

        lua_setmetatable(L, -2);

        return true;
    }

    explicit UserdataPtr(void* p)
    {
        // Can't construct with a null pointer!
        assert(p != nullptr);

        m_p = p;
    }
};

//============================================================================
/**
 * @brief Wraps a container that references a class object.
 *
 * The template argument C is the container type, ContainerTraits must be specialized on C or else a compile error will result.
 */
template <class C>
class UserdataShared : public Userdata
{
public:
    UserdataShared(const UserdataShared&) = delete;
    UserdataShared& operator=(const UserdataShared&) = delete;

    ~UserdataShared() = default;

    /**
     * @brief Construct from a container to the class or a derived class.
     *
     * @tparam U A container type.
     *
     * @param  u A container object reference.
     */
    template <class U>
    explicit UserdataShared(const U& u) : m_c(u)
    {
        m_p = const_cast<void*>(reinterpret_cast<const void*>((ContainerTraits<C>::get(m_c))));
    }

    /**
     * @brief Construct from a pointer to the class or a derived class.
     *
     * @tparam U A container type.
     *
     * @param u A container object pointer.
     */
    template <class U>
    explicit UserdataShared(U* u) : m_c(u)
    {
        m_p = const_cast<void*>(reinterpret_cast<const void*>((ContainerTraits<C>::get(m_c))));
    }

private:
    C m_c;
};

//=================================================================================================
/**
 * @brief SFINAE helper for non-const objects.
 */
template <class C, bool MakeObjectConst>
struct UserdataSharedHelper
{
    using T = std::remove_const_t<typename ContainerTraits<C>::Type>;

    static bool push(lua_State* L, const C& c, std::error_code& ec)
    {
        if (ContainerTraits<C>::get(c) != nullptr)
        {
            auto* us = new (lua_newuserdata_x<UserdataShared<C>>(L, sizeof(UserdataShared<C>))) UserdataShared<C>(c);

            lua_rawgetp(L, LUA_REGISTRYINDEX, getClassRegistryKey<T>());

            if (!lua_istable(L, -1))
            {
                lua_pop(L, 1); // possibly: a nil

                us->~UserdataShared<C>();

                ec = throw_or_error_code<LuaException>(L, ErrorCode::ClassNotRegistered);

                return false;
            }

            lua_setmetatable(L, -2);
        }
        else
        {
            lua_pushnil(L);
        }

        return true;
    }

    static bool push(lua_State* L, T* t, std::error_code& ec)
    {
        if (t)
        {
            auto* us = new (lua_newuserdata_x<UserdataShared<C>>(L, sizeof(UserdataShared<C>))) UserdataShared<C>(t);

            lua_rawgetp(L, LUA_REGISTRYINDEX, getClassRegistryKey<T>());

            if (!lua_istable(L, -1))
            {
                lua_pop(L, 1); // possibly: a nil

                us->~UserdataShared<C>();

                ec = throw_or_error_code<LuaException>(L, ErrorCode::ClassNotRegistered);

                return false;
            }

            lua_setmetatable(L, -2);
        }
        else
        {
            lua_pushnil(L);
        }

        return true;
    }
};

/**
 * @brief SFINAE helper for const objects.
 */
template <class C>
struct UserdataSharedHelper<C, true>
{
    using T = std::remove_const_t<typename ContainerTraits<C>::Type>;

    static bool push(lua_State* L, const C& c, std::error_code& ec)
    {
        if (ContainerTraits<C>::get(c) != nullptr)
        {
            auto* us = new (lua_newuserdata_x<UserdataShared<C>>(L, sizeof(UserdataShared<C>))) UserdataShared<C>(c);

            lua_rawgetp(L, LUA_REGISTRYINDEX, getConstRegistryKey<T>());

            if (!lua_istable(L, -1))
            {
                lua_pop(L, 1); // possibly: a nil

                us->~UserdataShared<C>();

                ec = throw_or_error_code<LuaException>(L, ErrorCode::ClassNotRegistered);

                return false;
            }

            lua_setmetatable(L, -2);
        }
        else
        {
            lua_pushnil(L);
        }

        return true;
    }

    static bool push(lua_State* L, T* t, std::error_code& ec)
    {
        if (t)
        {
            auto* us = new (lua_newuserdata_x<UserdataShared<C>>(L, sizeof(UserdataShared<C>))) UserdataShared<C>(t);

            lua_rawgetp(L, LUA_REGISTRYINDEX, getConstRegistryKey<T>());

            if (!lua_istable(L, -1))
            {
                lua_pop(L, 1); // possibly: a nil

                us->~UserdataShared<C>();

                ec = throw_or_error_code<LuaException>(L, ErrorCode::ClassNotRegistered);

                return false;
            }

            lua_setmetatable(L, -2);
        }
        else
        {
            lua_pushnil(L);
        }

        return true;
    }
};

//=================================================================================================
/**
 * @brief Pass by container.
 *
 * The container controls the object lifetime. Typically this will be a lifetime shared by C++ and Lua using a reference count. Because of type
 * erasure, containers like std::shared_ptr will not work, unless the type hold by them is derived from std::enable_shared_from_this.
 */
template <class T, bool ByContainer>
struct StackHelper
{
    using ReturnType = std::remove_const_t<typename ContainerTraits<T>::Type>;

    static bool push(lua_State* L, const T& t, std::error_code& ec)
    {
        return UserdataSharedHelper<T, std::is_const_v<typename ContainerTraits<T>::Type>>::push(L, t, ec);
    }

    static T get(lua_State* L, int index)
    {
        return ContainerTraits<T>::construct(Userdata::get<ReturnType>(L, index, true));
    }
};

/**
 * @brief Pass by value.
 *
 * Lifetime is managed by Lua. A C++ function which accesses a pointer or reference to an object outside the activation record in which it was
 * retrieved may result in undefined behavior if Lua garbage collected it.
 */
template <class T>
struct StackHelper<T, false>
{
    static bool push(lua_State* L, const T& t, std::error_code& ec)
    {
        return UserdataValue<T>::push(L, t, ec);
    }

    static bool push(lua_State* L, T&& t, std::error_code& ec)
    {
        return UserdataValue<T>::push(L, std::move(t), ec);
    }

    static T const& get(lua_State* L, int index)
    {
        return *Userdata::get<T>(L, index, true);
    }
};

//=================================================================================================
/**
 * @brief Lua stack conversions for pointers and references to class objects.
 *
 * Lifetime is managed by C++. Lua code which remembers a reference to the value may result in undefined behavior if C++ destroys the object.
 * The handling of the const and volatile qualifiers happens in UserdataPtr.
 */
template <class C, bool ByContainer>
struct RefStackHelper
{
    using ReturnType = C;
    using T = std::remove_const_t<typename ContainerTraits<C>::Type>;

    static bool push(lua_State* L, const C& t, std::error_code& ec)
    {
        return UserdataSharedHelper<C, std::is_const_v<typename ContainerTraits<C>::Type>>::push(L, t, ec);
    }

    static ReturnType get(lua_State* L, int index)
    {
        return ContainerTraits<C>::construct(Userdata::get<T>(L, index, true));
    }
};

template <class T>
struct RefStackHelper<T, false>
{
    using ReturnType = T&;

    static bool push(lua_State* L, const T& t, std::error_code& ec)
    {
        return UserdataPtr::push(L, &t, ec);
    }

    static ReturnType get(lua_State* L, int index)
    {
        T* t = Userdata::get<T>(L, index, true);

        if (!t)
            luaL_error(L, "nil passed to reference");

        return *t;
    }
};

//=================================================================================================
/**
 * @brief Trait class that selects whether to return a user registered class object by value or by reference.
 */
template <class T, class Enable = void>
struct UserdataGetter
{
    using ReturnType = T*;

    static ReturnType get(lua_State* L, int index)
    {
        return Userdata::get<T>(L, index, false);
    }
};

template <class T>
struct UserdataGetter<T, std::void_t<T (*)()>>
{
    using ReturnType = T;

    static ReturnType get(lua_State* L, int index)
    {
        return StackHelper<T, IsContainer<T>::value>::get(L, index);
    }
};

} // namespace detail

//=================================================================================================
/**
 * @brief Lua stack conversions for class objects passed by value.
 */
template <class T, class = void>
struct Stack
{
    using IsUserdata = void;

    using Getter = detail::UserdataGetter<T>;
    using ReturnType = typename Getter::ReturnType;

    [[nodiscard]] static bool push(lua_State* L, const T& value, std::error_code& ec)
    {
        return detail::StackHelper<T, detail::IsContainer<T>::value>::push(L, value, ec);
    }

    [[nodiscard]] static bool push(lua_State* L, T&& value, std::error_code& ec)
    {
        return detail::StackHelper<T, detail::IsContainer<T>::value>::push(L, std::move(value), ec);
    }

    [[nodiscard]] static ReturnType get(lua_State* L, int index)
    {
        return Getter::get(L, index);
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return detail::Userdata::isInstance<T>(L, index);
    }
};

namespace detail {

//=================================================================================================
/**
 * @brief Trait class indicating whether the parameter type must be a user registered class.
 *
 * The trait checks the existence of member type Stack::IsUserdata specialization for detection.
 */
template <class T, class Enable = void>
struct IsUserdata : std::false_type
{
};

template <class T>
struct IsUserdata<T, std::void_t<typename Stack<T>::IsUserdata>> : std::true_type
{
};

//=================================================================================================
/**
 * @brief Trait class that selects a specific push/get implementation for userdata.
 */
template <class T, bool IsUserdata>
struct StackOpSelector;

// pointer
template <class T>
struct StackOpSelector<T*, true>
{
    using ReturnType = T*;

    static bool push(lua_State* L, T* value, std::error_code& ec) { return UserdataPtr::push(L, value, ec); }

    static T* get(lua_State* L, int index) { return Userdata::get<T>(L, index, false); }

    static bool isInstance(lua_State* L, int index) { return Userdata::isInstance<T>(L, index); }
};

// pointer to const
template <class T>
struct StackOpSelector<const T*, true>
{
    using ReturnType = const T*;

    static bool push(lua_State* L, const T* value, std::error_code& ec) { return UserdataPtr::push(L, value, ec); }

    static const T* get(lua_State* L, int index) { return Userdata::get<T>(L, index, true); }

    static bool isInstance(lua_State* L, int index) { return Userdata::isInstance<T>(L, index); }
};

// l-value reference
template <class T>
struct StackOpSelector<T&, true>
{
    using Helper = RefStackHelper<T, IsContainer<T>::value>;
    using ReturnType = typename Helper::ReturnType;

    static bool push(lua_State* L, T& value, std::error_code& ec) { return UserdataPtr::push(L, &value, ec); }

    static ReturnType get(lua_State* L, int index) { return Helper::get(L, index); }

    static bool isInstance(lua_State* L, int index) { return Userdata::isInstance<T>(L, index); }
};

// l-value reference to const
template <class T>
struct StackOpSelector<const T&, true>
{
    using Helper = RefStackHelper<T, IsContainer<T>::value>;
    using ReturnType = typename Helper::ReturnType;

    static bool push(lua_State* L, const T& value, std::error_code& ec) { return Helper::push(L, value, ec); }

    static ReturnType get(lua_State* L, int index) { return Helper::get(L, index); }

    static bool isInstance(lua_State* L, int index) { return Userdata::isInstance<T>(L, index); }
};

} // namespace detail
} // namespace luabridge

// End File: Source/LuaBridge/detail/Userdata.h

// Begin File: Source/LuaBridge/detail/Stack.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2019, Dmitry Tarakanov
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// Copyright 2007, Nathan Reed
// SPDX-License-Identifier: MIT

namespace luabridge {

//=================================================================================================
/**
 * @brief Lua stack traits for C++ types.
 *
 * @tparam T A C++ type.
 */
template <class T, class>
struct Stack;

//=================================================================================================
/**
 * @brief Specialization for void type.
 */
template <>
struct Stack<void>
{
    [[nodiscard]] static bool push(lua_State*, std::error_code&)
    {
        return true;
    }
};

//=================================================================================================
/**
 * @brief Specialization for nullptr_t.
 */
template <>
struct Stack<std::nullptr_t>
{
    [[nodiscard]] static bool push(lua_State* L, std::nullptr_t, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif
        
        lua_pushnil(L);
        return true;
    }

    [[nodiscard]] static std::nullptr_t get(lua_State*, int)
    {
        return nullptr;
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_isnil(L, index);
    }
};

//=================================================================================================
/**
 * @brief Receive the lua_State* as an argument.
 */
template <>
struct Stack<lua_State*>
{
    [[nodiscard]] static lua_State* get(lua_State* L, int)
    {
        return L;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for a lua_CFunction.
 */
template <>
struct Stack<lua_CFunction>
{
    [[nodiscard]] static bool push(lua_State* L, lua_CFunction f, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        lua_pushcfunction_x(L, f);
        return true;
    }

    [[nodiscard]] static lua_CFunction get(lua_State* L, int index)
    {
        return lua_tocfunction(L, index);
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_iscfunction(L, index);
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `bool`.
 */
template <>
struct Stack<bool>
{
    [[nodiscard]] static bool push(lua_State* L, bool value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        lua_pushboolean(L, value ? 1 : 0);
        return true;
    }

    [[nodiscard]] static bool get(lua_State* L, int index)
    {
        return lua_toboolean(L, index) ? true : false;
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_isboolean(L, index);
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `std::byte`.
 */
template <>
struct Stack<std::byte>
{
    static_assert(sizeof(std::byte) < sizeof(lua_Integer));

    [[nodiscard]] static bool push(lua_State* L, std::byte value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        pushunsigned(L, std::to_integer<std::make_unsigned_t<lua_Integer>>(value));
        return true;
    }

    [[nodiscard]] static std::byte get(lua_State* L, int index)
    {
        return static_cast<std::byte>(luaL_checkinteger(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_integral_representable_by<unsigned char>(L, index);

        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `char`.
 */
template <>
struct Stack<char>
{
    [[nodiscard]] static bool push(lua_State* L, char value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        lua_pushlstring(L, &value, 1);
        return true;
    }

    [[nodiscard]] static char get(lua_State* L, int index)
    {
        return luaL_checkstring(L, index)[0];
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TSTRING)
        {
            std::size_t len;
            luaL_checklstring(L, index, &len);
            return len == 1;
        }
        
        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `int8_t`.
 */
template <>
struct Stack<int8_t>
{
    static_assert(sizeof(int8_t) < sizeof(lua_Integer));

    [[nodiscard]] static bool push(lua_State* L, int8_t value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        lua_pushinteger(L, static_cast<lua_Integer>(value));
        return true;
    }

    [[nodiscard]] static int8_t get(lua_State* L, int index)
    {
        return static_cast<int8_t>(luaL_checkinteger(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_integral_representable_by<int8_t>(L, index);

        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `unsigned char`.
 */
template <>
struct Stack<unsigned char>
{
    static_assert(sizeof(unsigned char) < sizeof(lua_Integer));

    [[nodiscard]] static bool push(lua_State* L, unsigned char value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        pushunsigned(L, value);
        return true;
    }

    [[nodiscard]] static unsigned char get(lua_State* L, int index)
    {
        return static_cast<unsigned char>(luaL_checkinteger(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_integral_representable_by<unsigned char>(L, index);

        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `short`.
 */
template <>
struct Stack<short>
{
    static_assert(sizeof(short) < sizeof(lua_Integer));

    [[nodiscard]] static bool push(lua_State* L, short value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        lua_pushinteger(L, static_cast<lua_Integer>(value));
        return true;
    }

    [[nodiscard]] static short get(lua_State* L, int index)
    {
        return static_cast<short>(luaL_checkinteger(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_integral_representable_by<short>(L, index);

        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `unsigned short`.
 */
template <>
struct Stack<unsigned short>
{
    static_assert(sizeof(unsigned short) < sizeof(lua_Integer));
    
    [[nodiscard]] static bool push(lua_State* L, unsigned short value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        pushunsigned(L, value);
        return true;
    }

    [[nodiscard]] static unsigned short get(lua_State* L, int index)
    {
        return static_cast<unsigned short>(luaL_checkinteger(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_integral_representable_by<unsigned short>(L, index);

        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `int`.
 */
template <>
struct Stack<int>
{
    [[nodiscard]] static bool push(lua_State* L, int value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        if (! is_integral_representable_by(value))
        {
            ec = makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger);
            return false;
        }

        lua_pushinteger(L, static_cast<lua_Integer>(value));
        return true;
    }

    [[nodiscard]] static int get(lua_State* L, int index)
    {
        return static_cast<int>(luaL_checkinteger(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_integral_representable_by<int>(L, index);

        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `unsigned int`.
 */
template <>
struct Stack<unsigned int>
{
    [[nodiscard]] static bool push(lua_State* L, unsigned int value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        if (! is_integral_representable_by(value))
        {
            ec = makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger);
            return false;
        }
        
        pushunsigned(L, value);
        return true;
    }

    [[nodiscard]] static uint32_t get(lua_State* L, int index)
    {
        return static_cast<unsigned int>(luaL_checkinteger(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_integral_representable_by<unsigned int>(L, index);

        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `long`.
 */
template <>
struct Stack<long>
{
    [[nodiscard]] static bool push(lua_State* L, long value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        if (! is_integral_representable_by(value))
        {
            ec = makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger);
            return false;
        }

        lua_pushinteger(L, static_cast<lua_Integer>(value));
        return true;
    }

    [[nodiscard]] static long get(lua_State* L, int index)
    {
        return static_cast<long>(luaL_checkinteger(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_integral_representable_by<long>(L, index);

        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `unsigned long`.
 */
template <>
struct Stack<unsigned long>
{
    [[nodiscard]] static bool push(lua_State* L, unsigned long value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        if (! is_integral_representable_by(value))
        {
            ec = makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger);
            return false;
        }

        pushunsigned(L, value);
        return true;
    }

    [[nodiscard]] static unsigned long get(lua_State* L, int index)
    {
        return static_cast<unsigned long>(luaL_checkinteger(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_integral_representable_by<unsigned long>(L, index);

        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `long long`.
 */
template <>
struct Stack<long long>
{
    [[nodiscard]] static bool push(lua_State* L, long long value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        if (! is_integral_representable_by(value))
        {
            ec = makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger);
            return false;
        }

        lua_pushinteger(L, static_cast<lua_Integer>(value));
        return true;
    }

    [[nodiscard]] static long long get(lua_State* L, int index)
    {
        return static_cast<long long>(luaL_checkinteger(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_integral_representable_by<long long>(L, index);

        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `unsigned long long`.
 */
template <>
struct Stack<unsigned long long>
{
    [[nodiscard]] static bool push(lua_State* L, unsigned long long value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        if (! is_integral_representable_by(value))
        {
            ec = makeErrorCode(ErrorCode::IntegerDoesntFitIntoLuaInteger);
            return false;
        }

        pushunsigned(L, value);
        return true;
    }

    [[nodiscard]] static unsigned long long get(lua_State* L, int index)
    {
        return static_cast<unsigned long long>(luaL_checkinteger(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_integral_representable_by<unsigned long long>(L, index);
        
        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `float`.
 */
template <>
struct Stack<float>
{
    [[nodiscard]] static bool push(lua_State* L, float value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        if (! is_floating_point_representable_by(value))
        {
            ec = makeErrorCode(ErrorCode::FloatingPointDoesntFitIntoLuaNumber);
            return false;
        }

        lua_pushnumber(L, static_cast<lua_Number>(value));
        return true;
    }

    [[nodiscard]] static float get(lua_State* L, int index)
    {
        return static_cast<float>(luaL_checknumber(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_floating_point_representable_by<float>(L, index);

        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `double`.
 */
template <>
struct Stack<double>
{
    [[nodiscard]] static bool push(lua_State* L, double value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        if (! is_floating_point_representable_by(value))
        {
            ec = makeErrorCode(ErrorCode::FloatingPointDoesntFitIntoLuaNumber);
            return false;
        }

        lua_pushnumber(L, static_cast<lua_Number>(value));
        return true;
    }

    [[nodiscard]] static double get(lua_State* L, int index)
    {
        return static_cast<double>(luaL_checknumber(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_floating_point_representable_by<double>(L, index);

        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `long double`.
 */
template <>
struct Stack<long double>
{
    [[nodiscard]] static bool push(lua_State* L, long double value, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        if (! is_floating_point_representable_by(value))
        {
            ec = makeErrorCode(ErrorCode::FloatingPointDoesntFitIntoLuaNumber);
            return false;
        }

        lua_pushnumber(L, static_cast<lua_Number>(value));
        return true;
    }

    [[nodiscard]] static long double get(lua_State* L, int index)
    {
        return static_cast<long double>(luaL_checknumber(L, index));
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNUMBER)
            return is_floating_point_representable_by<long double>(L, index);

        return false;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `const char*`.
 */
template <>
struct Stack<const char*>
{
    [[nodiscard]] static bool push(lua_State* L, const char* str, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        if (str != nullptr)
            lua_pushstring(L, str);
        else
            lua_pushlstring(L, "", 0);

        return true;
    }

    [[nodiscard]] static const char* get(lua_State* L, int index)
    {
        return luaL_checkstring(L, index);
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_type(L, index) == LUA_TSTRING;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `std::string_view`.
 */
template <>
struct Stack<std::string_view>
{
    [[nodiscard]] static bool push(lua_State* L, std::string_view str, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        lua_pushlstring(L, str.data(), str.size());
        return true;
    }

    [[nodiscard]] static std::string_view get(lua_State* L, int index)
    {
        return luaL_checkstring(L, index);
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_type(L, index) == LUA_TSTRING;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `std::string`.
 */
template <>
struct Stack<std::string>
{
    [[nodiscard]] static bool push(lua_State* L, const std::string& str, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        lua_pushlstring(L, str.data(), str.size());
        return true;
    }

    [[nodiscard]] static std::string get(lua_State* L, int index)
    {
        std::size_t len;
        if (lua_type(L, index) == LUA_TSTRING)
        {
            const char* str = lua_tolstring(L, index, &len);
            return std::string(str, len);
        }

        // Lua reference manual:
        // If the value is a number, then lua_tolstring also changes the actual value in the stack
        // to a string. (This change confuses lua_next when lua_tolstring is applied to keys during
        // a table traversal.)
        lua_pushvalue(L, index);
        const char* str = lua_tolstring(L, -1, &len);
        std::string string(str, len);
        lua_pop(L, 1); // Pop the temporary string
        return string;
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_type(L, index) == LUA_TSTRING;
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `std::optional`.
 */
template <class T>
struct Stack<std::optional<T>>
{
    using Type = std::optional<T>;
    
    [[nodiscard]] static bool push(lua_State* L, const Type& value, std::error_code& ec)
    {
        if (value)
            return Stack<T>::push(L, *value, ec);

#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        lua_pushnil(L);
        return true;
    }

    [[nodiscard]] static Type get(lua_State* L, int index)
    {
        if (lua_type(L, index) == LUA_TNIL)
            return std::nullopt;
        
        return Stack<T>::get(L, index);
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_isnil(L, index) || Stack<T>::isInstance(L, index);
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `std::tuple`.
 */
template <class... Types>
struct Stack<std::tuple<Types...>>
{
    [[nodiscard]] static bool push(lua_State* L, const std::tuple<Types...>& t, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 3))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        lua_createtable(L, static_cast<int>(Size), 0);

        return push_element(L, t, ec);
    }

    [[nodiscard]] static std::tuple<Types...> get(lua_State* L, int index)
    {
        if (!lua_istable(L, index))
            luaL_error(L, "#%d argment must be a table", index);

        if (get_length(L, index) != static_cast<int>(Size))
            luaL_error(L, "table size should be %d but is %d", static_cast<unsigned>(Size), get_length(L, index));

        std::tuple<Types...> value;

        int absIndex = lua_absindex(L, index);
        lua_pushnil(L);

        pop_element(L, absIndex, value);

        return value;
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_type(L, index) == LUA_TTABLE && get_length(L, index) == static_cast<int>(Size);
    }

private:
    static constexpr std::size_t Size = std::tuple_size_v<std::tuple<Types...>>;

    template <std::size_t Index = 0>
    static auto push_element(lua_State*, const std::tuple<Types...>&, std::error_code&)
        -> std::enable_if_t<Index == sizeof...(Types), bool>
    {
        return true;
    }

    template <std::size_t Index = 0>
    static auto push_element(lua_State* L, const std::tuple<Types...>& t, std::error_code& ec)
        -> std::enable_if_t<Index < sizeof...(Types), bool>
    {
        using T = std::tuple_element_t<Index, std::tuple<Types...>>;

        lua_pushinteger(L, static_cast<lua_Integer>(Index + 1));

        std::error_code push_ec;
        bool result = Stack<T>::push(L, std::get<Index>(t), push_ec);
        if (! result)
        {
            lua_pushnil(L);
            lua_settable(L, -3);
            ec = push_ec;
            return false;
        }

        lua_settable(L, -3);

        return push_element<Index + 1>(L, t, ec);
    }

    template <std::size_t Index = 0>
    static auto pop_element(lua_State*, int, std::tuple<Types...>&)
        -> std::enable_if_t<Index == sizeof...(Types)>
    {
    }

    template <std::size_t Index = 0>
    static auto pop_element(lua_State* L, int absIndex, std::tuple<Types...>& t)
        -> std::enable_if_t<Index < sizeof...(Types)>
    {
        using T = std::tuple_element_t<Index, std::tuple<Types...>>;

        if (lua_next(L, absIndex) == 0)
            return;

        std::get<Index>(t) = Stack<T>::get(L, -1);
        lua_pop(L, 1);

        pop_element<Index + 1>(L, absIndex, t);
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `T[N]`.
 */
template <class T, std::size_t N>
struct Stack<T[N]>
{
    static_assert(N > 0, "Unsupported zero sized array");

    [[nodiscard]] static bool push(lua_State* L, const T (&value)[N], std::error_code& ec)
    {
        if constexpr (std::is_same_v<T, char>)
        {
#if LUABRIDGE_SAFE_STACK_CHECKS
            if (! lua_checkstack(L, 1))
            {
                ec = makeErrorCode(ErrorCode::LuaStackOverflow);
                return false;
            }
#endif

            lua_pushlstring(L, value, N - 1);
            return true;
        }

#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 2))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        const int initialStackSize = lua_gettop(L);

        lua_createtable(L, static_cast<int>(N), 0);

        for (std::size_t i = 0; i < N; ++i)
        {
            lua_pushinteger(L, static_cast<lua_Integer>(i + 1));

            std::error_code push_ec;
            bool result = Stack<T>::push(L, value[i], push_ec);
            if (! result)
            {
                ec = push_ec;
                lua_pop(L, lua_gettop(L) - initialStackSize);
                return false;
            }

            lua_settable(L, -3);
        }

        return true;
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_type(L, index) == LUA_TTABLE && get_length(L, index) == static_cast<int>(N);
    }
};

namespace detail {

template <class T>
struct StackOpSelector<T&, false>
{
    using ReturnType = T;

    static bool push(lua_State* L, T& value, std::error_code& ec) { return Stack<T>::push(L, value, ec); }

    static ReturnType get(lua_State* L, int index) { return Stack<T>::get(L, index); }

    static bool isInstance(lua_State* L, int index) { return Stack<T>::isInstance(L, index); }
};

template <class T>
struct StackOpSelector<const T&, false>
{
    using ReturnType = T;

    static bool push(lua_State* L, const T& value, std::error_code& ec) { return Stack<T>::push(L, value, ec); }

    static auto get(lua_State* L, int index) { return Stack<T>::get(L, index); }

    static bool isInstance(lua_State* L, int index) { return Stack<T>::isInstance(L, index); }
};

template <class T>
struct StackOpSelector<T*, false>
{
    using ReturnType = T;

    static bool push(lua_State* L, T* value, std::error_code& ec) { return Stack<T>::push(L, *value, ec); }

    static ReturnType get(lua_State* L, int index) { return Stack<T>::get(L, index); }

    static bool isInstance(lua_State* L, int index) { return Stack<T>::isInstance(L, index); }
};

template <class T>
struct StackOpSelector<const T*, false>
{
    using ReturnType = T;

    static bool push(lua_State* L, const T* value, std::error_code& ec) { return Stack<T>::push(L, *value, ec); }

    static ReturnType get(lua_State* L, int index) { return Stack<T>::get(L, index); }

    static bool isInstance(lua_State* L, int index) { return Stack<T>::isInstance(L, index); }
};

} // namespace detail

template <class T>
struct Stack<T&, std::enable_if_t<!std::is_array_v<T&>>>
{
    using Helper = detail::StackOpSelector<T&, detail::IsUserdata<T>::value>;
    using ReturnType = typename Helper::ReturnType;

    [[nodiscard]] static bool push(lua_State* L, T& value, std::error_code& ec) { return Helper::push(L, value, ec); }

    [[nodiscard]] static ReturnType get(lua_State* L, int index) { return Helper::get(L, index); }

    [[nodiscard]] static bool isInstance(lua_State* L, int index) { return Helper::template isInstance<T>(L, index); }
};

template <class T>
struct Stack<const T&, std::enable_if_t<!std::is_array_v<const T&>>>
{
    using Helper = detail::StackOpSelector<const T&, detail::IsUserdata<T>::value>;
    using ReturnType = typename Helper::ReturnType;

    [[nodiscard]] static bool push(lua_State* L, const T& value, std::error_code& ec) { return Helper::push(L, value, ec); }

    [[nodiscard]] static auto get(lua_State* L, int index) { return Helper::get(L, index); }

    [[nodiscard]] static bool isInstance(lua_State* L, int index) { return Helper::template isInstance<T>(L, index); }
};

template <class T>
struct Stack<T*>
{
    using Helper = detail::StackOpSelector<T*, detail::IsUserdata<T>::value>;
    using ReturnType = typename Helper::ReturnType;

    [[nodiscard]] static bool push(lua_State* L, T* value, std::error_code& ec) { return Helper::push(L, value, ec); }

    [[nodiscard]] static ReturnType get(lua_State* L, int index) { return Helper::get(L, index); }

    [[nodiscard]] static bool isInstance(lua_State* L, int index) { return Helper::template isInstance<T>(L, index); }
};

template<class T>
struct Stack<const T*>
{
    using Helper = detail::StackOpSelector<const T*, detail::IsUserdata<T>::value>;
    using ReturnType = typename Helper::ReturnType;

    [[nodiscard]] static bool push(lua_State* L, const T* value, std::error_code& ec) { return Helper::push(L, value, ec); }

    [[nodiscard]] static ReturnType get(lua_State* L, int index) { return Helper::get(L, index); }

    [[nodiscard]] static bool isInstance(lua_State* L, int index) { return Helper::template isInstance<T>(L, index); }
};

//=================================================================================================
/**
 * @brief Push an object onto the Lua stack.
 */
template <class T>
[[nodiscard]] bool push(lua_State* L, const T& t, std::error_code& ec)
{
    return Stack<T>::push(L, t, ec);
}

//=================================================================================================
/**
 * @brief Get an object from the Lua stack.
 */
template <class T>
[[nodiscard]] T get(lua_State* L, int index)
{
    return Stack<T>::get(L, index);
}

//=================================================================================================
/**
 * @brief Check whether an object on the Lua stack is of type T.
 */
template <class T>
[[nodiscard]] bool isInstance(lua_State* L, int index)
{
    return Stack<T>::isInstance(L, index);
}

//=================================================================================================
/**
 * @brief Stack restorer.
 */
class StackRestore final
{
public:
    StackRestore(lua_State* L)
        : m_L(L)
        , m_stackTop(lua_gettop(L))
    {
    }

    ~StackRestore()
    {
        lua_settop(m_L, m_stackTop);
    }

private:
    lua_State* const m_L = nullptr;
    int m_stackTop = 0;
};

} // namespace luabridge

// End File: Source/LuaBridge/detail/Stack.h

// Begin File: Source/LuaBridge/Vector.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2018, Dmitry Tarakanov
// SPDX-License-Identifier: MIT

namespace luabridge {

//=================================================================================================
/**
 * @brief Stack specialization for `std::vector`.
 */
template <class T>
struct Stack<std::vector<T>>
{
    using Type = std::vector<T>;

    [[nodiscard]] static bool push(lua_State* L, const Type& vector, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 3))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        const int initialStackSize = lua_gettop(L);
        
        lua_createtable(L, static_cast<int>(vector.size()), 0);

        for (std::size_t i = 0; i < vector.size(); ++i)
        {
            lua_pushinteger(L, static_cast<lua_Integer>(i + 1));
            
            std::error_code errorCode;
            if (! Stack<T>::push(L, vector[i], errorCode))
            {
                ec = errorCode;
                lua_pop(L, lua_gettop(L) - initialStackSize);
                return false;
            }
            
            lua_settable(L, -3);
        }
        
        return true;
    }

    [[nodiscard]] static Type get(lua_State* L, int index)
    {
        if (!lua_istable(L, index))
            luaL_error(L, "#%d argument must be a table", index);

        Type vector;
        vector.reserve(static_cast<std::size_t>(get_length(L, index)));

        int absIndex = lua_absindex(L, index);
        lua_pushnil(L);

        while (lua_next(L, absIndex) != 0)
        {
            vector.emplace_back(Stack<T>::get(L, -1));
            lua_pop(L, 1);
        }

        return vector;
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_istable(L, index);
    }
};

} // namespace luabridge

// End File: Source/LuaBridge/Vector.h

// Begin File: Source/LuaBridge/UnorderedMap.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2019, Dmitry Tarakanov
// SPDX-License-Identifier: MIT

namespace luabridge {

//=================================================================================================
/**
 * @brief Stack specialization for `std::unordered_map`.
 */
template <class K, class V>
struct Stack<std::unordered_map<K, V>>
{
    using Type = std::unordered_map<K, V>;

    [[nodiscard]] static bool push(lua_State* L, const Type& map, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 3))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        const int initialStackSize = lua_gettop(L);
        
        lua_createtable(L, 0, static_cast<int>(map.size()));

        for (auto it = map.begin(); it != map.end(); ++it)
        {
            std::error_code errorCodeKey;
            if (! Stack<K>::push(L, it->first, errorCodeKey))
            {
                ec = errorCodeKey;
                lua_pop(L, lua_gettop(L) - initialStackSize);
                return false;
            }
            
            std::error_code errorCodeValue;
            if (! Stack<V>::push(L, it->second, errorCodeValue))
            {
                ec = errorCodeValue;
                lua_pop(L, lua_gettop(L) - initialStackSize);
                return false;
            }

            lua_settable(L, -3);
        }
        
        return true;
    }

    [[nodiscard]] static Type get(lua_State* L, int index)
    {
        if (!lua_istable(L, index))
            luaL_error(L, "#%d argument must be a table", index);

        Type map;

        int absIndex = lua_absindex(L, index);
        lua_pushnil(L);

        while (lua_next(L, absIndex) != 0)
        {
            map.emplace(Stack<K>::get(L, -2), Stack<V>::get(L, -1));
            lua_pop(L, 1);
        }

        return map;
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_istable(L, index);
    }
};

} // namespace luabridge

// End File: Source/LuaBridge/UnorderedMap.h

// Begin File: Source/LuaBridge/Set.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// SPDX-License-Identifier: MIT

namespace luabridge {

//=================================================================================================
/**
 * @brief Stack specialization for `std::set`.
 */
template <class K, class V>
struct Stack<std::set<K, V>>
{
    using Type = std::set<K, V>;
    
    [[nodiscard]] static bool push(lua_State* L, const Type& set, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 3))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        const int initialStackSize = lua_gettop(L);
        
        lua_createtable(L, 0, static_cast<int>(set.size()));

        for (auto it = set.begin(); it != set.end(); ++it)
        {
            std::error_code errorCodeKey;
            if (! Stack<K>::push(L, it->first, errorCodeKey))
            {
                ec = errorCodeKey;
                lua_pop(L, lua_gettop(L) - initialStackSize);
                return false;
            }

            std::error_code errorCodeValue;
            if (! Stack<V>::push(L, it->second, errorCodeValue))
            {
                ec = errorCodeValue;
                lua_pop(L, lua_gettop(L) - initialStackSize);
                return false;
            }

            lua_settable(L, -3);
        }
        
        return true;
    }

    [[nodiscard]] static Type get(lua_State* L, int index)
    {
        if (!lua_istable(L, index))
            luaL_error(L, "#%d argument must be a table", index);

        Type set;

        int absIndex = lua_absindex(L, index);
        lua_pushnil(L);

        while (lua_next(L, absIndex) != 0)
        {
            set.emplace(Stack<K>::get(L, -2), Stack<V>::get(L, -1));
            lua_pop(L, 1);
        }

        return set;
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_istable(L, index);
    }
};

} // namespace luabridge

// End File: Source/LuaBridge/Set.h

// Begin File: Source/LuaBridge/RefCountedObject.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// Copyright 2004-11 by Raw Material Software Ltd.
// SPDX-License-Identifier: MIT

//==============================================================================
/*
  This is a derivative work used by permission from part of
  JUCE, available at http://www.rawaterialsoftware.com

  License: The MIT License (http://www.opensource.org/licenses/mit-license.php)

  Permission is hereby granted, free of charge, to any person obtaining a copy
  of this software and associated documentation files (the "Software"), to deal
  in the Software without restriction, including without limitation the rights
  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  copies of the Software, and to permit persons to whom the Software is
  furnished to do so, subject to the following conditions:

  The above copyright notice and this permission notice shall be included in all
  copies or substantial portions of the Software.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  SOFTWARE.

  This file incorporates work covered by the following copyright and
  permission notice:

    This file is part of the JUCE library - "Jules' Utility Class Extensions"
    Copyright 2004-11 by Raw Material Software Ltd.
*/
//==============================================================================

namespace luabridge {

//==============================================================================
/**
  Adds reference-counting to an object.

  To add reference-counting to a class, derive it from this class, and
  use the RefCountedObjectPtr class to point to it.

  e.g. @code
  class MyClass : public RefCountedObjectType
  {
      void foo();

      // This is a neat way of declaring a typedef for a pointer class,
      // rather than typing out the full templated name each time..
      typedef RefCountedObjectPtr<MyClass> Ptr;
  };

  MyClass::Ptr p = new MyClass();
  MyClass::Ptr p2 = p;
  p = 0;
  p2->foo();
  @endcode

  Once a new RefCountedObjectType has been assigned to a pointer, be
  careful not to delete the object manually.
*/
template<class CounterType>
class RefCountedObjectType
{
public:
    //==============================================================================
    /** Increments the object's reference count.

        This is done automatically by the smart pointer, but is public just
        in case it's needed for nefarious purposes.
    */
    inline void incReferenceCount() const { ++refCount; }

    /** Decreases the object's reference count.

        If the count gets to zero, the object will be deleted.
    */
    inline void decReferenceCount() const
    {
        assert(getReferenceCount() > 0);

        if (--refCount == 0)
            delete this;
    }

    /** Returns the object's current reference count.
     * @returns The reference count.
     */
    inline int getReferenceCount() const { return static_cast<int>(refCount); }

protected:
    //==============================================================================
    /** Creates the reference-counted object (with an initial ref count of zero). */
    RefCountedObjectType() : refCount() {}

    /** Destructor. */
    virtual ~RefCountedObjectType()
    {
        // it's dangerous to delete an object that's still referenced by something else!
        assert(getReferenceCount() == 0);
    }

private:
    //==============================================================================
    CounterType mutable refCount;
};

//==============================================================================

/** Non thread-safe reference counted object.

    This creates a RefCountedObjectType that uses a non-atomic integer
    as the counter.
*/
typedef RefCountedObjectType<int> RefCountedObject;

//==============================================================================
/**
    A smart-pointer class which points to a reference-counted object.

    The template parameter specifies the class of the object you want to point
    to - the easiest way to make a class reference-countable is to simply make
    it inherit from RefCountedObjectType, but if you need to, you could roll
    your own reference-countable class by implementing a pair of methods called
    incReferenceCount() and decReferenceCount().

    When using this class, you'll probably want to create a typedef to
    abbreviate the full templated name - e.g.

    @code

    typedef RefCountedObjectPtr <MyClass> MyClassPtr;

    @endcode
*/
template<class ReferenceCountedObjectClass>
class RefCountedObjectPtr
{
public:
    /** The class being referenced by this pointer. */
    typedef ReferenceCountedObjectClass ReferencedType;

    //==============================================================================
    /** Creates a pointer to a null object. */
    inline RefCountedObjectPtr() : referencedObject(0) {}

    /** Creates a pointer to an object.
        This will increment the object's reference-count if it is non-null.

        @param refCountedObject A reference counted object to own.
    */
    inline RefCountedObjectPtr(ReferenceCountedObjectClass* const refCountedObject)
        : referencedObject(refCountedObject)
    {
        if (refCountedObject != 0)
            refCountedObject->incReferenceCount();
    }

    /** Copies another pointer.
        This will increment the object's reference-count (if it is non-null).

        @param other Another pointer.
    */
    inline RefCountedObjectPtr(const RefCountedObjectPtr& other)
        : referencedObject(other.referencedObject)
    {
        if (referencedObject != 0)
            referencedObject->incReferenceCount();
    }

    /**
      Takes-over the object from another pointer.

      @param other Another pointer.
    */
    inline RefCountedObjectPtr(RefCountedObjectPtr&& other)
        : referencedObject(other.referencedObject)
    {
        other.referencedObject = 0;
    }

    /** Copies another pointer.
        This will increment the object's reference-count (if it is non-null).

        @param other Another pointer.
    */
    template<class DerivedClass>
    inline RefCountedObjectPtr(const RefCountedObjectPtr<DerivedClass>& other)
        : referencedObject(static_cast<ReferenceCountedObjectClass*>(other.getObject()))
    {
        if (referencedObject != 0)
            referencedObject->incReferenceCount();
    }

    /** Changes this pointer to point at a different object.

        The reference count of the old object is decremented, and it might be
        deleted if it hits zero. The new object's count is incremented.

        @param other A pointer to assign from.
        @returns This pointer.
    */
    RefCountedObjectPtr& operator=(const RefCountedObjectPtr& other)
    {
        return operator=(other.referencedObject);
    }

    /** Changes this pointer to point at a different object.
        The reference count of the old object is decremented, and it might be
        deleted if it hits zero. The new object's count is incremented.

        @param other A pointer to assign from.
        @returns This pointer.
    */
    template<class DerivedClass>
    RefCountedObjectPtr& operator=(const RefCountedObjectPtr<DerivedClass>& other)
    {
        return operator=(static_cast<ReferenceCountedObjectClass*>(other.getObject()));
    }

    /**
      Takes-over the object from another pointer.

      @param other A pointer to assign from.
      @returns This pointer.
     */
    RefCountedObjectPtr& operator=(RefCountedObjectPtr&& other)
    {
        std::swap(referencedObject, other.referencedObject);
        return *this;
    }

    /** Changes this pointer to point at a different object.
        The reference count of the old object is decremented, and it might be
        deleted if it hits zero. The new object's count is incremented.

        @param newObject A reference counted object to own.
        @returns This pointer.
    */
    RefCountedObjectPtr& operator=(ReferenceCountedObjectClass* const newObject)
    {
        if (referencedObject != newObject)
        {
            if (newObject != 0)
                newObject->incReferenceCount();

            ReferenceCountedObjectClass* const oldObject = referencedObject;
            referencedObject = newObject;

            if (oldObject != 0)
                oldObject->decReferenceCount();
        }

        return *this;
    }

    /** Destructor.
        This will decrement the object's reference-count, and may delete it if it
        gets to zero.
    */
    ~RefCountedObjectPtr()
    {
        if (referencedObject != 0)
            referencedObject->decReferenceCount();
    }

    /** Returns the object that this pointer references.
        The returned pointer may be null.

        @returns The pointee.
    */
    operator ReferenceCountedObjectClass*() const { return referencedObject; }

    /** Returns the object that this pointer references.
        The returned pointer may be null.

        @returns The pointee.
    */
    ReferenceCountedObjectClass* operator->() const { return referencedObject; }

    /** Returns the object that this pointer references.
        The returned pointer may be null.

        @returns The pointee.
    */
    ReferenceCountedObjectClass* getObject() const { return referencedObject; }

private:
    //==============================================================================
    ReferenceCountedObjectClass* referencedObject;
};

/** Compares two ReferenceCountedObjectPointers. */
template<class ReferenceCountedObjectClass>
bool operator==(const RefCountedObjectPtr<ReferenceCountedObjectClass>& object1,
                ReferenceCountedObjectClass* const object2)
{
    return object1.getObject() == object2;
}

/** Compares two ReferenceCountedObjectPointers. */
template<class ReferenceCountedObjectClass>
bool operator==(const RefCountedObjectPtr<ReferenceCountedObjectClass>& object1,
                const RefCountedObjectPtr<ReferenceCountedObjectClass>& object2)
{
    return object1.getObject() == object2.getObject();
}

/** Compares two ReferenceCountedObjectPointers. */
template<class ReferenceCountedObjectClass>
bool operator==(ReferenceCountedObjectClass* object1,
                RefCountedObjectPtr<ReferenceCountedObjectClass>& object2)
{
    return object1 == object2.getObject();
}

/** Compares two ReferenceCountedObjectPointers. */
template<class ReferenceCountedObjectClass>
bool operator!=(const RefCountedObjectPtr<ReferenceCountedObjectClass>& object1,
                const ReferenceCountedObjectClass* object2)
{
    return object1.getObject() != object2;
}

/** Compares two ReferenceCountedObjectPointers. */
template<class ReferenceCountedObjectClass>
bool operator!=(const RefCountedObjectPtr<ReferenceCountedObjectClass>& object1,
                RefCountedObjectPtr<ReferenceCountedObjectClass>& object2)
{
    return object1.getObject() != object2.getObject();
}

/** Compares two ReferenceCountedObjectPointers. */
template<class ReferenceCountedObjectClass>
bool operator!=(ReferenceCountedObjectClass* object1,
                RefCountedObjectPtr<ReferenceCountedObjectClass>& object2)
{
    return object1 != object2.getObject();
}

//==============================================================================

template<class T>
struct ContainerTraits<RefCountedObjectPtr<T>>
{
    using Type = T;

    static RefCountedObjectPtr<T> construct(T* c) { return c; }

    static T* get(RefCountedObjectPtr<T> const& c) { return c.getObject(); }
};

//==============================================================================

} // namespace luabridge

// End File: Source/LuaBridge/RefCountedObject.h

// Begin File: Source/LuaBridge/Map.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2018, Dmitry Tarakanov
// SPDX-License-Identifier: MIT

namespace luabridge {

//=================================================================================================
/**
 * @brief Stack specialization for `std::map`.
 */
template <class K, class V>
struct Stack<std::map<K, V>>
{
    using Type = std::map<K, V>;

    [[nodiscard]] static bool push(lua_State* L, const Type& map, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 3))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        const int initialStackSize = lua_gettop(L);

        lua_createtable(L, 0, static_cast<int>(map.size()));

        for (auto it = map.begin(); it != map.end(); ++it)
        {
            std::error_code errorCodeKey;
            if (! Stack<K>::push(L, it->first, errorCodeKey))
            {
                ec = errorCodeKey;
                lua_pop(L, lua_gettop(L) - initialStackSize);
                return false;
            }

            std::error_code errorCodeValue;
            if (! Stack<V>::push(L, it->second, errorCodeValue))
            {
                ec = errorCodeValue;
                lua_pop(L, lua_gettop(L) - initialStackSize);
                return false;
            }

            lua_settable(L, -3);
        }
        
        return true;
    }

    [[nodiscard]] static Type get(lua_State* L, int index)
    {
        if (!lua_istable(L, index))
            luaL_error(L, "#%d argument must be a table", index);

        Type map;

        int absIndex = lua_absindex(L, index);
        lua_pushnil(L);

        while (lua_next(L, absIndex) != 0)
        {
            map.emplace(Stack<K>::get(L, -2), Stack<V>::get(L, -1));
            lua_pop(L, 1);
        }

        return map;
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_istable(L, index);
    }
};

} // namespace luabridge

// End File: Source/LuaBridge/Map.h

// Begin File: Source/LuaBridge/detail/FuncTraits.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2020, Dmitry Tarakanov
// Copyright 2019, George Tokmaji
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// SPDX-License-Identifier: MIT

namespace luabridge {
namespace detail {

//=================================================================================================
/**
 * @brief Generic function traits.
 *
 * @tparam IsMember True if the function is a member function pointer.
 * @tparam IsConst True if the function is const.
 * @tparam R Return type of the function.
 * @tparam Args Arguments types as variadic parameter pack.
 */
template <bool IsMember, bool IsConst, class R, class... Args>
struct function_traits_base
{
    using result_type = R;

    using argument_types = std::tuple<Args...>;

    static constexpr auto arity = sizeof...(Args);

    static constexpr auto is_member = IsMember;

    static constexpr auto is_const = IsConst;
};

template <class...>
struct function_traits_impl;

template <class R, class... Args>
struct function_traits_impl<R(Args...)> : function_traits_base<false, false, R, Args...>
{
};

template <class R, class... Args>
struct function_traits_impl<R (*)(Args...)> : function_traits_base<false, false, R, Args...>
{
};

template <class C, class R, class... Args>
struct function_traits_impl<R (C::*)(Args...)> : function_traits_base<true, false, R, Args...>
{
};

template <class C, class R, class... Args>
struct function_traits_impl<R (C::*)(Args...) const> : function_traits_base<true, true, R, Args...>
{
};

template <class R, class... Args>
struct function_traits_impl<R(Args...) noexcept> : function_traits_base<false, false, R, Args...>
{
};

template <class R, class... Args>
struct function_traits_impl<R (*)(Args...) noexcept> : function_traits_base<false, false, R, Args...>
{
};

template <class C, class R, class... Args>
struct function_traits_impl<R (C::*)(Args...) noexcept> : function_traits_base<true, false, R, Args...>
{
};

template <class C, class R, class... Args>
struct function_traits_impl<R (C::*)(Args...) const noexcept> : function_traits_base<true, true, R, Args...>
{
};

#if _MSC_VER && _M_IX86 // Windows: WINAPI (a.k.a. __stdcall) function pointers (32bit only).
template <class R, class... Args>
struct function_traits_impl<R __stdcall(Args...)> : function_traits_base<false, false, R, Args...>
{
};

template <class R, class... Args>
struct function_traits_impl<R (__stdcall *)(Args...)> : function_traits_base<false, false, R, Args...>
{
};

template <class C, class R, class... Args>
struct function_traits_impl<R (__stdcall C::*)(Args...)> : function_traits_base<true, false, R, Args...>
{
};

template <class C, class R, class... Args>
struct function_traits_impl<R (__stdcall C::*)(Args...) const> : function_traits_base<true, true, R, Args...>
{
};

template <class R, class... Args>
struct function_traits_impl<R __stdcall(Args...) noexcept> : function_traits_base<false, false, R, Args...>
{
};

template <class R, class... Args>
struct function_traits_impl<R (__stdcall *)(Args...) noexcept> : function_traits_base<false, false, R, Args...>
{
};

template <class C, class R, class... Args>
struct function_traits_impl<R (__stdcall C::*)(Args...) noexcept> : function_traits_base<true, false, R, Args...>
{
};

template <class C, class R, class... Args>
struct function_traits_impl<R (__stdcall C::*)(Args...) const noexcept> : function_traits_base<true, true, R, Args...>
{
};
#endif

template <class F>
struct functor_traits_impl : function_traits_impl<decltype(&F::operator())>
{
};

//=================================================================================================
/**
 * @brief Traits class for callable objects (e.g. function pointers, lambdas)
 *
 * @tparam F Callable object.
 */
template <class F>
struct function_traits : std::conditional_t<std::is_class_v<F>,
                                            detail::functor_traits_impl<F>,
                                            detail::function_traits_impl<F>>
{
};

//=================================================================================================
/**
 * @brief Deduces the return type of a callble object.
 *
 * @tparam F Callable object.
 */
template <class F>
using function_result_t = typename function_traits<F>::result_type;

/**
 * @brief Deduces the argument type of a callble object.
 *
 * @tparam I Argument index.
 * @tparam F Callable object.
 */
template <std::size_t I, class F>
using function_argument_t = std::tuple_element_t<I, typename function_traits<F>::argument_types>;

/**
 * @brief Deduces the arguments type of a callble object.
 *
 * @tparam F Callable object.
 */
template <class F>
using function_arguments_t = typename function_traits<F>::argument_types;

/**
 * @brief An integral constant expression that gives the number of arguments accepted by the callable object.
 *
 * @tparam F Callable object.
 */
template <class F>
static constexpr std::size_t function_arity_v = function_traits<F>::arity;

/**
 * @brief An boolean constant expression that checks if the callable object is a member function.
 *
 * @tparam F Callable object.
 */
template <class F>
static constexpr bool function_is_member_v = function_traits<F>::is_member;

/**
 * @brief An boolean constant expression that checks if the callable object is const.
 *
 * @tparam F Callable object.
 */
template <class F>
static constexpr bool function_is_const_v = function_traits<F>::is_const;

//=================================================================================================
/**
 * @brief Detect if we are a `std::function`.
 *
 * @tparam F Callable object.
 */
template <class F, class...>
struct is_std_function : std::false_type
{
};

template <class ReturnType, class... Args>
struct is_std_function<std::function<ReturnType(Args...)>> : std::true_type
{
};

template <class Signature>
struct is_std_function<std::function<Signature>> : std::true_type
{
};

template <class F>
static constexpr bool is_std_function_v = is_std_function<F>::value;

//=================================================================================================
/**
 * @brief Reconstruct a function signature from return type and args.
 */
template <class, class...>
struct to_std_function_type
{
};

template <class ReturnType, class... Args>
struct to_std_function_type<ReturnType, std::tuple<Args...>>
{
    using type = std::function<ReturnType(Args...)>;
};

template <class ReturnType, class... Args>
using to_std_function_type_t = typename to_std_function_type<ReturnType, Args...>::type;

//=================================================================================================
/**
 * @brief Simple make_tuple alternative that doesn't decay the types.
 *
 * @tparam Types Argument types that will compose the tuple.
 */
template <class... Types>
constexpr auto tupleize(Types&&... types)
{
    return std::tuple<Types...>(std::forward<Types>(types)...);
}

//=================================================================================================
/**
 * @brief Make argument lists extracting them from the lua state, starting at a stack index.
 *
 * @tparam ArgsPack Arguments pack to extract from the lua stack.
 * @tparam Start Start index where stack variables are located in the lua stack.
 */
template <class ArgsPack, std::size_t Start, std::size_t... Indices>
auto make_arguments_list_impl(lua_State* L, std::index_sequence<Indices...>)
{
    return tupleize(Stack<std::tuple_element_t<Indices, ArgsPack>>::get(L, Start + Indices)...);
}

template <class ArgsPack, std::size_t Start>
auto make_arguments_list(lua_State* L)
{
    return make_arguments_list_impl<ArgsPack, Start>(L, std::make_index_sequence<std::tuple_size_v<ArgsPack>>());
}

//=================================================================================================
/**
 * @brief Helpers for iterating through tuple arguments, pushing each argument to the lua stack.
 */
template <std::size_t Index = 0, class... Types>
auto push_arguments(lua_State*, std::tuple<Types...>, std::error_code&)
    -> std::enable_if_t<Index == sizeof...(Types), std::size_t>
{
    return Index + 1;
}

template <std::size_t Index = 0, class... Types>
auto push_arguments(lua_State* L, std::tuple<Types...> t, std::error_code& ec)
    -> std::enable_if_t<Index < sizeof...(Types), std::size_t>
{
    using T = std::tuple_element_t<Index, std::tuple<Types...>>;

    std::error_code pec;
    bool result = Stack<T>::push(L, std::get<Index>(t), pec);
    if (! result)
    {
        ec = pec;
        return Index + 1;
    }

    return push_arguments<Index + 1, Types...>(L, std::move(t), ec);
}

//=================================================================================================
/**
 * @brief Helpers for iterating through tuple arguments, popping each argument from the lua stack.
 */
template <std::ptrdiff_t Start, std::ptrdiff_t Index = 0, class... Types>
auto pop_arguments(lua_State*, std::tuple<Types...>&)
    -> std::enable_if_t<Index == sizeof...(Types), std::size_t>
{
    return sizeof...(Types);
}

template <std::ptrdiff_t Start, std::ptrdiff_t Index = 0, class... Types>
auto pop_arguments(lua_State* L, std::tuple<Types...>& t)
    -> std::enable_if_t<Index < sizeof...(Types), std::size_t>
{
    using T = std::tuple_element_t<Index, std::tuple<Types...>>;

    std::get<Index>(t) = Stack<T>::get(L, Start - Index);

    return pop_arguments<Start, Index + 1, Types...>(L, t);
}

//=================================================================================================
/**
 * @brief Remove first type from tuple.
 */
template <class T>
struct remove_first_type
{
};

template <class T, class... Ts>
struct remove_first_type<std::tuple<T, Ts...>>
{
    using type = std::tuple<Ts...>;
};

template <class T>
using remove_first_type_t = typename remove_first_type<T>::type;

//=================================================================================================
/**
 * @brief Function generator.
 */
template <class ReturnType, class ArgsPack, std::size_t Start = 1u>
struct function
{
    template <class F>
    static int call(lua_State* L, F func)
    {
#if LUABRIDGE_HAS_EXCEPTIONS
        try
        {
#endif
            std::error_code ec;
            bool result = Stack<ReturnType>::push(L, std::apply(func, make_arguments_list<ArgsPack, Start>(L)), ec);
            if (! result)
                luaL_error(L, "%s", ec.message().c_str());

#if LUABRIDGE_HAS_EXCEPTIONS
        }
        catch (const std::exception& e)
        {
            luaL_error(L, "%s", e.what());
        }
        catch (...)
        {
            luaL_error(L, "Error while calling function");
        }
#endif

        return 1;
    }

    template <class T, class F>
    static int call(lua_State* L, T* ptr, F func)
    {
#if LUABRIDGE_HAS_EXCEPTIONS
        try
        {
#endif
            auto f = [ptr, func](auto&&... args) -> ReturnType { return (ptr->*func)(std::forward<decltype(args)>(args)...); };

            std::error_code ec;
            bool result = Stack<ReturnType>::push(L, std::apply(f, make_arguments_list<ArgsPack, Start>(L)), ec);
            if (! result)
                luaL_error(L, "%s", ec.message().c_str());

#if LUABRIDGE_HAS_EXCEPTIONS
        }
        catch (const std::exception& e)
        {
            luaL_error(L, "%s", e.what());
        }
        catch (...)
        {
            luaL_error(L, "Error while calling method");
        }
#endif

        return 1;
    }
};

template <class ArgsPack, std::size_t Start>
struct function<void, ArgsPack, Start>
{
    template <class F>
    static int call(lua_State* L, F func)
    {
#if LUABRIDGE_HAS_EXCEPTIONS
        try
        {
#endif
            std::apply(func, make_arguments_list<ArgsPack, Start>(L));

#if LUABRIDGE_HAS_EXCEPTIONS
        }
        catch (const std::exception& e)
        {
            luaL_error(L, "%s", e.what());
        }
        catch (...)
        {
            luaL_error(L, "Error while calling function");
        }
#endif

        return 0;
    }

    template <class T, class F>
    static int call(lua_State* L, T* ptr, F func)
    {
#if LUABRIDGE_HAS_EXCEPTIONS
        try
        {
#endif
            auto f = [ptr, func](auto&&... args) { (ptr->*func)(std::forward<decltype(args)>(args)...); };

            std::apply(f, make_arguments_list<ArgsPack, Start>(L));

#if LUABRIDGE_HAS_EXCEPTIONS
        }
        catch (const std::exception& e)
        {
            luaL_error(L, "%s", e.what());
        }
        catch (...)
        {
            luaL_error(L, "Error while calling method");
        }
#endif

        return 0;
    }
};

//=================================================================================================
/**
 * @brief Constructor generators.
 *
 * These templates call operator new with the contents of a type/value list passed to the constructor. Two versions of call() are provided.
 * One performs a regular new, the other performs a placement new.
 */
template <class T, class Args>
struct constructor;

template <class T>
struct constructor<T, void>
{
    using empty = std::tuple<>;

    static T* call(const empty&)
    {
        return new T;
    }

    static T* call(void* ptr, const empty&)
    {
        return new (ptr) T;
    }
};

template <class T, class Args>
struct constructor
{
    static T* call(const Args& args)
    {
        auto alloc = [](auto&&... args) { return new T{ std::forward<decltype(args)>(args)... }; };

        return std::apply(alloc, args);
    }

    static T* call(void* ptr, const Args& args)
    {
        auto alloc = [ptr](auto&&... args) { return new (ptr) T{ std::forward<decltype(args)>(args)... }; };

        return std::apply(alloc, args);
    }
};

//=================================================================================================
/**
 * @brief Factory generators.
 */
template <class T>
struct factory
{
    template <class F, class Args>
    static T* call(void* ptr, const F& func, const Args& args)
    {
        auto alloc = [ptr, &func](auto&&... args) { return func(ptr, std::forward<decltype(args)>(args)...); };

        return std::apply(alloc, args);
    }

    template <class F>
    static T* call(void* ptr, const F& func)
    {
        return func(ptr);
    }
};

} // namespace detail
} // namespace luabridge

// End File: Source/LuaBridge/detail/FuncTraits.h

// Begin File: Source/LuaBridge/detail/CFunctions.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2019, Dmitry Tarakanov
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// SPDX-License-Identifier: MIT

namespace luabridge {
namespace detail {

//=================================================================================================
/**
 * @brief __index metamethod for a namespace or class static and non-static members.
 *
 * Retrieves functions from metatables and properties from propget tables. Looks through the class hierarchy if inheritance is present.
 */
inline int index_metamethod(lua_State* L)
{
#if LUABRIDGE_SAFE_STACK_CHECKS
    luaL_checkstack(L, 3, detail::error_lua_stack_overflow);
#endif

    assert(lua_istable(L, 1) || lua_isuserdata(L, 1)); // Stack (further not shown): table | userdata, name

    lua_getmetatable(L, 1); // Stack: class/const table (mt)
    assert(lua_istable(L, -1));

    for (;;)
    {
        lua_pushvalue(L, 2); // Stack: mt, field name
        lua_rawget(L, -2); // Stack: mt, field | nil

        if (lua_iscfunction(L, -1)) // Stack: mt, field
        {
            lua_remove(L, -2); // Stack: field
            return 1;
        }

        assert(lua_isnil(L, -1)); // Stack: mt, nil
        lua_pop(L, 1); // Stack: mt

        lua_rawgetp(L, -1, getPropgetKey()); // Stack: mt, propget table (pg)
        assert(lua_istable(L, -1));

        lua_pushvalue(L, 2); // Stack: mt, pg, field name
        lua_rawget(L, -2); // Stack: mt, pg, getter | nil
        lua_remove(L, -2); // Stack: mt, getter | nil

        if (lua_iscfunction(L, -1)) // Stack: mt, getter
        {
            lua_remove(L, -2); // Stack: getter
            lua_pushvalue(L, 1); // Stack: getter, table | userdata
            lua_call(L, 1, 1); // Stack: value
            return 1;
        }

        assert(lua_isnil(L, -1)); // Stack: mt, nil
        lua_pop(L, 1); // Stack: mt

        // It may mean that the field may be in const table and it's constness violation.
        // Don't check that, just return nil

        // Repeat the lookup in the parent metafield,
        // or return nil if the field doesn't exist.
        lua_rawgetp(L, -1, getParentKey()); // Stack: mt, parent mt | nil

        if (lua_isnil(L, -1)) // Stack: mt, nil
        {
            lua_remove(L, -2); // Stack: nil
            return 1;
        }

        // Removethe  metatable and repeat the search in the parent one.
        assert(lua_istable(L, -1)); // Stack: mt, parent mt
        lua_remove(L, -2); // Stack: parent mt
    }

    // no return
}

//=================================================================================================
/**
 * @brief __newindex metamethod for non-static members.
 *
 * Retrieves properties from propset tables.
 */
inline int newindex_metamethod(lua_State* L, bool pushSelf)
{
#if LUABRIDGE_SAFE_STACK_CHECKS
    luaL_checkstack(L, 3, detail::error_lua_stack_overflow);
#endif

    assert(lua_istable(L, 1) || lua_isuserdata(L, 1)); // Stack (further not shown): table | userdata, name, new value

    lua_getmetatable(L, 1); // Stack: metatable (mt)
    assert(lua_istable(L, -1));

    for (;;)
    {
        lua_rawgetp(L, -1, getPropsetKey()); // Stack: mt, propset table (ps) | nil

        if (lua_isnil(L, -1)) // Stack: mt, nil
        {
            lua_pop(L, 2); // Stack: -
            luaL_error(L, "No member named '%s'", lua_tostring(L, 2));
        }

        assert(lua_istable(L, -1));

        lua_pushvalue(L, 2); // Stack: mt, ps, field name
        lua_rawget(L, -2); // Stack: mt, ps, setter | nil
        lua_remove(L, -2); // Stack: mt, setter | nil

        if (lua_iscfunction(L, -1)) // Stack: mt, setter
        {
            lua_remove(L, -2); // Stack: setter
            if (pushSelf)
                lua_pushvalue(L, 1); // Stack: setter, table | userdata
            lua_pushvalue(L, 3); // Stack: setter, table | userdata, new value
            lua_call(L, pushSelf ? 2 : 1, 0); // Stack: -
            return 0;
        }

        assert(lua_isnil(L, -1)); // Stack: mt, nil
        lua_pop(L, 1); // Stack: mt

        lua_rawgetp(L, -1, getParentKey()); // Stack: mt, parent mt | nil

        if (lua_isnil(L, -1)) // Stack: mt, nil
        {
            lua_pop(L, 1); // Stack: -
            luaL_error(L, "No writable member '%s'", lua_tostring(L, 2));
        }

        assert(lua_istable(L, -1)); // Stack: mt, parent mt
        lua_remove(L, -2); // Stack: parent mt
        // Repeat the search in the parent
    }

    return 0;
}

//=================================================================================================
/**
 * @brief __newindex metamethod for objects.
 */
inline int newindex_object_metamethod(lua_State* L)
{
    return newindex_metamethod(L, true);
}

//=================================================================================================
/**
 * @brief __newindex metamethod for namespace or class static members.
 *
 * Retrieves properties from propset tables.
 */
inline int newindex_static_metamethod(lua_State* L)
{
    return newindex_metamethod(L, false);
}

//=================================================================================================
/**
 * @brief lua_CFunction to report an error writing to a read-only value.
 *
 * The name of the variable is in the first upvalue.
 */
inline int read_only_error(lua_State* L)
{
    std::string s;

    s = s + "'" + lua_tostring(L, lua_upvalueindex(1)) + "' is read-only";

    luaL_error(L, "%s", s.c_str());

    return 0;
}

//=================================================================================================
/**
 * @brief __gc metamethod for a class.
 */
template <class C>
static int gc_metamethod(lua_State* L)
{
    Userdata* ud = Userdata::getExact<C>(L, 1);
    assert(ud);

    ud->~Userdata();

    return 0;
}

//=================================================================================================

template <class T, class C = void>
struct property_getter;

/**
 * @brief lua_CFunction to get a variable.
 *
 * This is used for global variables or class static data members. The pointer to the data is in the first upvalue.
 */
template <class T>
struct property_getter<T, void>
{
    static int call(lua_State* L)
    {
        assert(lua_islightuserdata(L, lua_upvalueindex(1)));

        T* ptr = static_cast<T*>(lua_touserdata(L, lua_upvalueindex(1)));
        assert(ptr != nullptr);

        std::error_code ec;
        if (! Stack<T&>::push(L, *ptr, ec))
            luaL_error(L, "%s", ec.message().c_str());

        return 1;
    }
};

#if 0
template <class T>
struct property_getter<std::reference_wrapper<T>, void>
{
    static int call(lua_State* L)
    {
        assert(lua_islightuserdata(L, lua_upvalueindex(1)));

        std::reference_wrapper<T>* ptr = static_cast<std::reference_wrapper<T>*>(lua_touserdata(L, lua_upvalueindex(1)));
        assert(ptr != nullptr);

        std::error_code ec;
        if (! Stack<T&>::push(L, ptr->get(), ec))
            luaL_error(L, "%s", ec.message().c_str());

        return 1;
    }
};
#endif

/**
 * @brief lua_CFunction to get a class data member.
 *
 * The pointer-to-member is in the first upvalue. The class userdata object is at the top of the Lua stack.
 */
template <class T, class C>
struct property_getter
{
    static int call(lua_State* L)
    {
        C* c = Userdata::get<C>(L, 1, true);

        T C::** mp = static_cast<T C::**>(lua_touserdata(L, lua_upvalueindex(1)));

#if LUABRIDGE_HAS_EXCEPTIONS
        try
        {
#endif
            std::error_code ec;
            if (! Stack<T&>::push(L, c->**mp, ec))
                luaL_error(L, "%s", ec.message().c_str());

#if LUABRIDGE_HAS_EXCEPTIONS
        }
        catch (const std::exception& e)
        {
            luaL_error(L, "%s", e.what());
        }
        catch (...)
        {
            luaL_error(L, "Error while getting property");
        }
#endif

        return 1;
    }
};

/**
 * @brief Helper function to push a property getter on a table at a specific index.
 */
inline void add_property_getter(lua_State* L, const char* name, int tableIndex)
{
#if LUABRIDGE_SAFE_STACK_CHECKS
    luaL_checkstack(L, 2, detail::error_lua_stack_overflow);
#endif

    assert(name != nullptr);
    assert(lua_istable(L, tableIndex));
    assert(lua_iscfunction(L, -1)); // Stack: getter

    lua_rawgetp(L, tableIndex, getPropgetKey()); // Stack: getter, propget table (pg)
    lua_pushvalue(L, -2); // Stack: getter, pg, getter
    rawsetfield(L, -2, name); // Stack: getter, pg
    lua_pop(L, 2); // Stack: -
}

//=================================================================================================

template <class T, class C = void>
struct property_setter;

/**
 * @brief lua_CFunction to set a variable.
 *
 * This is used for global variables or class static data members. The pointer to the data is in the first upvalue.
 */
template <class T>
struct property_setter<T, void>
{
    static int call(lua_State* L)
    {
        assert(lua_islightuserdata(L, lua_upvalueindex(1)));

        T* ptr = static_cast<T*>(lua_touserdata(L, lua_upvalueindex(1)));
        assert(ptr != nullptr);

        *ptr = Stack<T>::get(L, 1);

        return 0;
    }
};

#if 0
template <class T>
struct property_setter<std::reference_wrapper<T>, void>
{
    static int call(lua_State* L)
    {
        assert(lua_islightuserdata(L, lua_upvalueindex(1)));

        std::reference_wrapper<T>* ptr = static_cast<std::reference_wrapper<T>*>(lua_touserdata(L, lua_upvalueindex(1)));
        assert(ptr != nullptr);

        ptr->get() = Stack<T>::get(L, 1);

        return 0;
    }
};
#endif

/**
 * @brief lua_CFunction to set a class data member.
 *
 * The pointer-to-member is in the first upvalue. The class userdata object is at the top of the Lua stack.
 */
template <class T, class C>
struct property_setter
{
    static int call(lua_State* L)
    {
        C* c = Userdata::get<C>(L, 1, false);

        T C::** mp = static_cast<T C::**>(lua_touserdata(L, lua_upvalueindex(1)));

#if LUABRIDGE_HAS_EXCEPTIONS
        try
        {
#endif
            c->** mp = Stack<T>::get(L, 2);

#if LUABRIDGE_HAS_EXCEPTIONS
        }
        catch (const std::exception& e)
        {
            luaL_error(L, "%s", e.what());
        }
        catch (...)
        {
            luaL_error(L, "Error while setting property");
        }
#endif

        return 0;
    }
};

/**
 * @brief Helper function to push a property setter on a table at a specific index.
 */
inline void add_property_setter(lua_State* L, const char* name, int tableIndex)
{
#if LUABRIDGE_SAFE_STACK_CHECKS
    luaL_checkstack(L, 2, detail::error_lua_stack_overflow);
#endif

    assert(name != nullptr);
    assert(lua_istable(L, tableIndex));
    assert(lua_iscfunction(L, -1)); // Stack: setter

    lua_rawgetp(L, tableIndex, getPropsetKey()); // Stack: setter, propset table (ps)
    lua_pushvalue(L, -2); // Stack: setter, ps, setter
    rawsetfield(L, -2, name); // Stack: setter, ps
    lua_pop(L, 2); // Stack: -
}

//=================================================================================================
/**
 * @brief lua_CFunction to call a class member function with a return value.
 *
 * The member function pointer is in the first upvalue. The class userdata object is at the top of the Lua stack.
 */
template <class F, class T>
int invoke_member_function(lua_State* L)
{
    using FnTraits = detail::function_traits<F>;

    assert(isfulluserdata(L, lua_upvalueindex(1)));

    T* ptr = Userdata::get<T>(L, 1, false);

    const F& func = *static_cast<const F*>(lua_touserdata(L, lua_upvalueindex(1)));
    assert(func != nullptr);

    return function<typename FnTraits::result_type, typename FnTraits::argument_types, 2>::call(L, ptr, func);
}

template <class F, class T>
int invoke_const_member_function(lua_State* L)
{
    using FnTraits = detail::function_traits<F>;

    assert(isfulluserdata(L, lua_upvalueindex(1)));

    const T* ptr = Userdata::get<T>(L, 1, true);

    const F& func = *static_cast<const F*>(lua_touserdata(L, lua_upvalueindex(1)));
    assert(func != nullptr);

    return function<typename FnTraits::result_type, typename FnTraits::argument_types, 2>::call(L, ptr, func);
}

//=================================================================================================
/**
 * @brief lua_CFunction to call a class member lua_CFunction.
 *
 * The member function pointer is in the first upvalue. The object userdata ('this') value is at top ot the Lua stack.
 */
template <class T>
int invoke_member_cfunction(lua_State* L)
{
    using F = int (T::*)(lua_State * L);

    assert(isfulluserdata(L, lua_upvalueindex(1)));

    T* t = Userdata::get<T>(L, 1, false);

    const F& func = *static_cast<const F*>(lua_touserdata(L, lua_upvalueindex(1)));
    assert(func != nullptr);

    return (t->*func)(L);
}

template <class T>
int invoke_const_member_cfunction(lua_State* L)
{
    using F = int (T::*)(lua_State * L) const;

    assert(isfulluserdata(L, lua_upvalueindex(1)));

    const T* t = Userdata::get<T>(L, 1, true);

    const F& func = *static_cast<const F*>(lua_touserdata(L, lua_upvalueindex(1)));
    assert(func != nullptr);

    return (t->*func)(L);
}

//=================================================================================================
/**
 * @brief lua_CFunction to call on a object via function pointer.
 *
 * The proxy function pointer (lightuserdata) is in the first upvalue. The class userdata object is at the top of the Lua stack.
 */
template <class F>
int invoke_proxy_function(lua_State* L)
{
    using FnTraits = detail::function_traits<F>;

    assert(lua_islightuserdata(L, lua_upvalueindex(1)));

    auto func = reinterpret_cast<F>(lua_touserdata(L, lua_upvalueindex(1)));
    assert(func != nullptr);

    return function<typename FnTraits::result_type, typename FnTraits::argument_types, 1>::call(L, func);
}

//=================================================================================================
/**
 * @brief lua_CFunction to call on a object via functor (lambda wrapped in a std::function).
 *
 * The proxy std::function (lightuserdata) is in the first upvalue. The class userdata object is at the top of the Lua stack.
 */
template <class F>
int invoke_proxy_functor(lua_State* L)
{
    using FnTraits = detail::function_traits<F>;

    assert(isfulluserdata(L, lua_upvalueindex(1)));

    auto& func = *align<F>(lua_touserdata(L, lua_upvalueindex(1)));

    return function<typename FnTraits::result_type, typename FnTraits::argument_types, 1>::call(L, func);
}

} // namespace detail
} // namespace luabridge

// End File: Source/LuaBridge/detail/CFunctions.h

// Begin File: Source/LuaBridge/detail/LuaRef.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2019, George Tokmaji
// Copyright 2018, Dmitry Tarakanov
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// Copyright 2008, Nigel Atkinson <suprapilot+LuaCode@gmail.com>
// SPDX-License-Identifier: MIT

namespace luabridge {

class LuaResult;

//=================================================================================================
/**
 * @brief Type tag for representing LUA_TNIL.
 *
 * Construct one of these using `LuaNil ()` to represent a Lua nil. This is faster than creating a reference in the registry to nil.
 * Example:
 *
 * @code
 *     LuaRef t (LuaRef::createTable (L));
 *     ...
 *     t ["k"] = LuaNil (); // assign nil
 * @endcode
 */
struct LuaNil
{
};

/**
 * @brief Stack specialization for LuaNil.
 */
template <>
struct Stack<LuaNil>
{
    [[nodiscard]] static bool push(lua_State* L, const LuaNil&, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        lua_pushnil(L);
        return true;
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_type(L, index) == LUA_TNIL;
    }
};

//=================================================================================================
/**
 * @brief Base class for Lua variables and table item reference classes.
 */
template <class Impl, class LuaRef>
class LuaRefBase
{
protected:
    //=============================================================================================
    /**
     * @brief Pop the Lua stack.
     *
     * Pops the specified number of stack items on destruction. We use this when returning objects, to avoid an explicit temporary
     * variable, since the destructor executes after the return statement. For example:
     *
     * @code
     *     template <class U>
     *     U cast (lua_State* L)
     *     {
     *         StackPop p (L, 1);
     *         ...
     *         return U (); // Destructor called after this line
     *     }
     * @endcode
     *
     * @note The `StackPop` object must always be a named local variable.
     */
    class StackPop
    {
    public:
        /**
         * @brief Create a StackPop object.
         *
         * @param L  A Lua state.
         * @param count The number of stack entries to pop on destruction.
         */
        StackPop(lua_State* L, int count)
            : m_L(L)
            , m_count(count) {
        }

        /**
         * @brief Destroy a StackPop object.
         *
         * In case an exception is in flight before the destructor is called, stack is potentially cleared by lua. So we never pop more than
         * the actual size of the stack.
         */
        ~StackPop()
        {
            const int stackSize = lua_gettop(m_L);

            lua_pop(m_L, stackSize < m_count ? stackSize : m_count);
        }

        /**
         * @brief Set a new number to pop.
         *
         * @param newCount The new number of stack entries to pop on destruction.
         */
        void popCount(int newCount)
        {
            m_count = newCount;
        }

    private:
        lua_State* m_L = nullptr;
        int m_count = 0;
    };

    friend struct Stack<LuaRef>;

    //=============================================================================================
    /**
     * @brief Type tag for stack construction.
     */
    struct FromStack
    {
    };

    LuaRefBase(lua_State* L)
        : m_L(L)
    {
    }

    //=============================================================================================
    /**
     * @brief Create a reference to this reference.
     *
     * @returns An index in the Lua registry.
     */
    int createRef() const
    {
        impl().push();

        return luaL_ref(m_L, LUA_REGISTRYINDEX);
    }

public:
    //=============================================================================================
    /**
     * @brief Convert to a string using lua_tostring function.
     *
     * @returns A string representation of the referred Lua value.
     */
    std::string tostring() const
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(m_L, 2))
            return {};
#endif

        StackPop p(m_L, 1);

        lua_getglobal(m_L, "tostring");

        impl().push();

        lua_call(m_L, 1, 1);

        const char* str = lua_tostring(m_L, -1);
        return str != nullptr ? str : "";
    }

    //=============================================================================================
    /**
     * @brief Print a text description of the value to a stream.
     *
     * This is used for diagnostics.
     *
     * @param os An output stream.
     */
    void print(std::ostream& os) const
    {
        switch (type())
        {
        case LUA_TNIL:
            os << "nil";
            break;

        case LUA_TNUMBER:
            os << cast<lua_Number>();
            break;

        case LUA_TBOOLEAN:
            os << (cast<bool>() ? "true" : "false");
            break;

        case LUA_TSTRING:
            os << '"' << cast<const char*>() << '"';
            break;

        case LUA_TTABLE:
            os << "table: " << tostring();
            break;

        case LUA_TFUNCTION:
            os << "function: " << tostring();
            break;

        case LUA_TUSERDATA:
            os << "userdata: " << tostring();
            break;

        case LUA_TTHREAD:
            os << "thread: " << tostring();
            break;

        case LUA_TLIGHTUSERDATA:
            os << "lightuserdata: " << tostring();
            break;

        default:
            os << "unknown";
            break;
        }
    }

    //=============================================================================================
    /**
     * @brief Insert a Lua value or table item reference to a stream.
     *
     * @param os  An output stream.
     * @param ref A Lua reference.
     *
     * @returns The output stream.
     */
    friend std::ostream& operator<<(std::ostream& os, const LuaRefBase& ref)
    {
        ref.print(os);
        return os;
    }

    //=============================================================================================
    /**
     * @brief Retrieve the lua_State associated with the reference.
     *
     * @returns A Lua state.
     */
    lua_State* state() const
    {
        return m_L;
    }

    //=============================================================================================
    /**
     * @brief Place the object onto the Lua stack.
     *
     * @param L A Lua state.
     */
    void push(lua_State* L) const
    {
        assert(equalstates(L, m_L));
        (void) L;

        impl().push();
    }

    //=============================================================================================
    /**
     * @brief Pop the top of Lua stack and assign it to the reference.
     *
     * @param L A Lua state.
     */
    void pop(lua_State* L)
    {
        assert(equalstates(L, m_L));
        (void) L;

        impl().pop();
    }

    //=============================================================================================
    /**
     * @brief Return the Lua type of the referred value.
     *
     * This invokes lua_type().
     *
     * @returns The type of the referred value.
     *
     * @see lua_type()
     */
    int type() const
    {
        StackPop p(m_L, 1);

        impl().push();

        const int refType = lua_type(m_L, -1);

        return refType;
    }

    /**
     * @brief Indicate whether it is a nil reference.
     *
     * @returns True if this is a nil reference, false otherwise.
     */
    bool isNil() const { return type() == LUA_TNIL; }

    /**
     * @brief Indicate whether it is a reference to a boolean.
     *
     * @returns True if it is a reference to a boolean, false otherwise.
     */
    bool isBool() const { return type() == LUA_TBOOLEAN; }

    /**
     * @brief Indicate whether it is a reference to a number.
     *
     * @returns True if it is a reference to a number, false otherwise.
     */
    bool isNumber() const { return type() == LUA_TNUMBER; }

    /**
     * @brief Indicate whether it is a reference to a string.
     *
     * @returns True if it is a reference to a string, false otherwise.
     */
    bool isString() const { return type() == LUA_TSTRING; }

    /**
     * @brief Indicate whether it is a reference to a table.
     *
     * @returns True if it is a reference to a table, false otherwise.
     */
    bool isTable() const { return type() == LUA_TTABLE; }

    /**
     * @brief Indicate whether it is a reference to a function.
     *
     * @returns True if it is a reference to a function, false otherwise.
     */
    bool isFunction() const { return type() == LUA_TFUNCTION; }

    /**
     * @brief Indicate whether it is a reference to a full userdata.
     *
     * @returns True if it is a reference to a full userdata, false otherwise.
     */
    bool isUserdata() const { return type() == LUA_TUSERDATA; }

    /**
     * @brief Indicate whether it is a reference to a lua thread (coroutine).
     *
     * @returns True if it is a reference to a lua thread, false otherwise.
     */
    bool isThread() const { return type() == LUA_TTHREAD; }

    /**
     * @brief Indicate whether it is a reference to a light userdata.
     *
     * @returns True if it is a reference to a light userdata, false otherwise.
     */
    bool isLightUserdata() const { return type() == LUA_TLIGHTUSERDATA; }

    /**
     * @brief Indicate whether it is a callable.
     *
     * @returns True if it is a callable, false otherwise.
     */
    bool isCallable() const
    {
        if (isFunction())
            return true;

        auto metatable = getMetatable();
        return metatable.isTable() && metatable["__call"].isFunction();
    }

    //=============================================================================================
    /**
     * @brief Perform an explicit conversion to the type T.
     *
     * @returns A value of the type T converted from this reference.
     */
    template <class T>
    T cast() const
    {
        StackPop p(m_L, 1);

        impl().push();

        if constexpr (std::is_enum_v<T>)
        {
            using U = std::underlying_type_t<T>;

            return static_cast<T>(Stack<U>::get(m_L, -1));
        }
        else
        {
            return Stack<T>::get(m_L, -1);
        }
    }

    //=============================================================================================
    /**
     * @brief Indicate if this reference is convertible to the type T.
     *
     * @returns True if the referred value is convertible to the type T, false otherwise.
     */
    template <class T>
    bool isInstance() const
    {
        StackPop p(m_L, 1);

        impl().push();

        if constexpr (std::is_enum_v<T>)
        {
            using U = std::underlying_type_t<T>;
            
            return Stack<U>::isInstance(m_L, -1);
        }
        else
        {
            return Stack<T>::isInstance(m_L, -1);
        }
    }

    //=============================================================================================
    /**
     * @brief Type cast operator.
     *
     * @returns A value of the type T converted from this reference.
     */
    template <class T>
    operator T() const
    {
        return cast<T>();
    }

    //=============================================================================================
    /**
     * @brief Get the metatable for the LuaRef.
     *
     * @returns A LuaRef holding the metatable of the lua object.
     */
    LuaRef getMetatable() const
    {
        if (isNil())
            return LuaRef(m_L);

        StackPop p(m_L, 2);

        impl().push();

        if (! lua_getmetatable(m_L, -1))
        {
            p.popCount(1);
            return LuaRef(m_L);
        }

        return LuaRef::fromStack(m_L);
    }

    //=============================================================================================
    /**
     * @brief Compare this reference with a specified value using lua_compare().
     *
     * This invokes metamethods.
     *
     * @param rhs A value to compare with.
     *
     * @returns True if the referred value is equal to the specified one.
     */
    template <class T>
    bool operator==(const T& rhs) const
    {
        StackPop p(m_L, 2);

        impl().push();

        std::error_code ec;
        if (! Stack<T>::push(m_L, rhs, ec))
        {
            p.popCount(1);
            return false;
        }

        return lua_compare(m_L, -2, -1, LUA_OPEQ) == 1;
    }

    /**
     * @brief Compare this reference with a specified value using lua_compare().
     *
     * This invokes metamethods.
     *
     * @param rhs A value to compare with.
     *
     * @returns True if the referred value is not equal to the specified one.
     */
    template <class T>
    bool operator!=(const T& rhs) const
    {
        return !(*this == rhs);
    }

    /**
     * @brief Compare this reference with a specified value using lua_compare().
     *
     * This invokes metamethods.
     *
     * @param rhs A value to compare with.
     *
     * @returns True if the referred value is less than the specified one.
     */
    template <class T>
    bool operator<(const T& rhs) const
    {
        StackPop p(m_L, 2);

        impl().push();

        std::error_code ec;
        if (! Stack<T>::push(m_L, rhs, ec))
        {
            p.popCount(1);
            return false;
        }

        const int lhsType = lua_type(m_L, -2);
        const int rhsType = lua_type(m_L, -1);
        if (lhsType != rhsType)
            return lhsType < rhsType;

        return lua_compare(m_L, -2, -1, LUA_OPLT) == 1;
    }

    /**
     * @brief Compare this reference with a specified value using lua_compare().
     *
     * This invokes metamethods.
     *
     * @param rhs A value to compare with.
     *
     * @returns True if the referred value is less than or equal to the specified one.
     */
    template <class T>
    bool operator<=(const T& rhs) const
    {
        StackPop p(m_L, 2);

        impl().push();

        std::error_code ec;
        if (! Stack<T>::push(m_L, rhs, ec))
        {
            p.popCount(1);
            return false;
        }

        const int lhsType = lua_type(m_L, -2);
        const int rhsType = lua_type(m_L, -1);
        if (lhsType != rhsType)
            return lhsType <= rhsType;

        return lua_compare(m_L, -2, -1, LUA_OPLE) == 1;
    }

    /**
     * @brief Compare this reference with a specified value using lua_compare().
     *
     * This invokes metamethods.
     *
     * @param rhs A value to compare with.
     *
     * @returns True if the referred value is greater than the specified one.
     */
    template <class T>
    bool operator>(const T& rhs) const
    {
        StackPop p(m_L, 2);

        impl().push();

        std::error_code ec;
        if (! Stack<T>::push(m_L, rhs, ec))
        {
            p.popCount(1);
            return false;
        }

        const int lhsType = lua_type(m_L, -2);
        const int rhsType = lua_type(m_L, -1);
        if (lhsType != rhsType)
            return lhsType > rhsType;

        return lua_compare(m_L, -1, -2, LUA_OPLT) == 1;
    }

    /**
     * @brief Compare this reference with a specified value using lua_compare().
     *
     * This invokes metamethods.
     *
     * @param rhs A value to compare with.
     *
     * @returns True if the referred value is greater than or equal to the specified one.
     */
    template <class T>
    bool operator>=(const T& rhs) const
    {
        StackPop p(m_L, 2);

        impl().push();

        std::error_code ec;
        if (! Stack<T>::push(m_L, rhs, ec))
        {
            p.popCount(1);
            return false;
        }

        const int lhsType = lua_type(m_L, -2);
        const int rhsType = lua_type(m_L, -1);
        if (lhsType != rhsType)
            return lhsType >= rhsType;

        return lua_compare(m_L, -1, -2, LUA_OPLE) == 1;
    }

    /**
     * @brief Compare this reference with a specified value using lua_compare().
     *
     * This does not invoke metamethods.
     *
     * @param rhs A value to compare with.
     *
     * @returns True if the referred value is equal to the specified one.
     */
    template <class T>
    bool rawequal(const T& v) const
    {
        StackPop p(m_L, 2);

        impl().push();

        std::error_code ec;
        if (! Stack<T>::push(m_L, v, ec))
        {
            p.popCount(1);
            return false;
        }

        return lua_rawequal(m_L, -1, -2) == 1;
    }

    //=============================================================================================
    /**
     * @brief Append a value to a referred table.
     *
     * If the table is a sequence this will add another element to it.
     *
     * @param v A value to append to the table.
     */
#if 0
    template <class T>
    void append(const T& v) const
    {
        StackPop p(m_L, 1);

        impl().push();

        std::error_code ec;
        if (! Stack<T>::push(m_L, v, ec))
            return;

        luaL_ref(m_L, -2);
    }
#endif
    
    //=============================================================================================
    /**
     * @brief Return the length of a referred array.
     *
     * This is identical to applying the Lua # operator.
     *
     * @returns The length of the referred array.
     */
    int length() const
    {
        StackPop p(m_L, 1);

        impl().push();

        return get_length(m_L, -1);
    }

    //=============================================================================================
    /**
     * @brief Call Lua code.
     *
     * The return value is provided as a LuaRef (which may be LUA_REFNIL).
     *
     * If an error occurs, a LuaException is thrown (only if exceptions are enabled).
     *
     * @returns A result of the call.
     */
    template <class... Args>
    LuaResult operator()(Args&&... args) const;

protected:
    lua_State* m_L = nullptr;

private:
    const Impl& impl() const { return static_cast<const Impl&>(*this); }

    Impl& impl() { return static_cast<Impl&>(*this); }
};

//=================================================================================================
/**
 * @brief Lightweight reference to a Lua object.
 *
 * The reference is maintained for the lifetime of the C++ object.
 */
class LuaRef : public LuaRefBase<LuaRef, LuaRef>
{
    //=============================================================================================
    /**
     * @brief A proxy for representing table values.
     */
    class TableItem : public LuaRefBase<TableItem, LuaRef>
    {
        friend class LuaRef;

    public:
        //=========================================================================================
        /**
         * @brief Construct a TableItem from a table value.
         *
         * The table is in the registry, and the key is at the top of the stack.
         * The key is popped off the stack.
         *
         * @param L A lua state.
         * @param tableRef The index of a table in the Lua registry.
         */
        TableItem(lua_State* L, int tableRef)
            : LuaRefBase(L)
            , m_keyRef(luaL_ref(L, LUA_REGISTRYINDEX))
        {
#if LUABRIDGE_SAFE_STACK_CHECKS
            luaL_checkstack(m_L, 1, detail::error_lua_stack_overflow);
#endif

            lua_rawgeti(m_L, LUA_REGISTRYINDEX, tableRef);
            m_tableRef = luaL_ref(L, LUA_REGISTRYINDEX);
        }

        //=========================================================================================
        /**
         * @brief Create a TableItem via copy constructor.
         *
         * It is best to avoid code paths that invoke this, because it creates an extra temporary Lua reference. Typically this is done by
         * passing the TableItem parameter as a `const` reference.
         *
         * @param other Another Lua table item reference.
         */
        TableItem(const TableItem& other)
            : LuaRefBase(other.m_L)
        {
#if LUABRIDGE_SAFE_STACK_CHECKS
            if (! lua_checkstack(m_L, 1))
                return;
#endif

            lua_rawgeti(m_L, LUA_REGISTRYINDEX, other.m_tableRef);
            m_tableRef = luaL_ref(m_L, LUA_REGISTRYINDEX);

            lua_rawgeti(m_L, LUA_REGISTRYINDEX, other.m_keyRef);
            m_keyRef = luaL_ref(m_L, LUA_REGISTRYINDEX);
        }

        //=========================================================================================
        /**
         * @brief Destroy the proxy.
         *
         * This does not destroy the table value.
         */
        ~TableItem()
        {
            if (m_keyRef != LUA_NOREF)
                luaL_unref(m_L, LUA_REGISTRYINDEX, m_keyRef);

            if (m_tableRef != LUA_NOREF)
                luaL_unref(m_L, LUA_REGISTRYINDEX, m_tableRef);
        }

        //=========================================================================================
        /**
         * @brief Assign a new value to this table key.
         *
         * This may invoke metamethods.
         *
         * @tparam T The type of a value to assing.
         *
         * @param v A value to assign.
         *
         * @returns This reference.
         */
        template <class T>
        TableItem& operator=(const T& v)
        {
#if LUABRIDGE_SAFE_STACK_CHECKS
            if (! lua_checkstack(m_L, 2))
                return *this;
#endif

            StackPop p(m_L, 1);

            lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_tableRef);
            lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_keyRef);

            std::error_code ec;
            if (! Stack<T>::push(m_L, v, ec))
                return *this;

            lua_settable(m_L, -3);
            return *this;
        }

        //=========================================================================================
        /**
         * @brief Assign a new value to this table key.
         *
         * The assignment is raw, no metamethods are invoked.
         *
         * @tparam T The type of a value to assing.
         *
         * @param v A value to assign.
         *
         * @returns This reference.
         */
        template <class T>
        TableItem& rawset(const T& v)
        {
#if LUABRIDGE_SAFE_STACK_CHECKS
            if (! lua_checkstack(m_L, 2))
                return *this;
#endif

            StackPop p(m_L, 1);

            lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_tableRef);
            lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_keyRef);

            std::error_code ec;
            if (! Stack<T>::push(m_L, v, ec))
                return *this;

            lua_rawset(m_L, -3);
            return *this;
        }

        //=========================================================================================
        /**
         * @brief Push the value onto the Lua stack.
         */
        using LuaRefBase::push;

        void push() const
        {
#if LUABRIDGE_SAFE_STACK_CHECKS
            if (! lua_checkstack(m_L, 3))
                return;
#endif

            lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_tableRef);
            lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_keyRef);
            lua_gettable(m_L, -2);
            lua_remove(m_L, -2); // remove the table
        }

        //=========================================================================================
        /**
         * @brief Access a table value using a key.
         *
         * This invokes metamethods.
         *
         * @tparam T The type of a key.
         *
         * @param key A key value.
         *
         * @returns A Lua table item reference.
         */
        template <class T>
        TableItem operator[](const T& key) const
        {
            return LuaRef(*this)[key];
        }

        //=========================================================================================
        /**
         * @brief Access a table value using a key.
         *
         * The operation is raw, metamethods are not invoked. The result is passed by value and may not be modified.
         *
         * @tparam T The type of a key.
         *
         * @param key A key value.
         *
         * @returns A Lua value reference.
         */
        template <class T>
        LuaRef rawget(const T& key) const
        {
            return LuaRef(*this).rawget(key);
        }

    private:
        int m_tableRef = LUA_NOREF;
        int m_keyRef = LUA_NOREF;
    };

    friend struct Stack<TableItem>;
    friend struct Stack<TableItem&>;

    //=========================================================================================
    /**
     * @brief Create a reference to an object at the top of the Lua stack and pop it.
     *
     * This constructor is private and not invoked directly. Instead, use the `fromStack` function.
     *
     * @param L A Lua state.
     *
     * @note The object is popped.
     */
    LuaRef(lua_State* L, FromStack)
        : LuaRefBase(L)
        , m_ref(luaL_ref(m_L, LUA_REGISTRYINDEX))
    {
    }

    //=========================================================================================
    /**
     * @brief Create a reference to an object on the Lua stack.
     *
     * This constructor is private and not invoked directly. Instead, use the `fromStack` function.
     *
     * @param L A Lua state.
     *
     * @param index The index of the value on the Lua stack.
     *
     * @note The object is not popped.
     */
    LuaRef(lua_State* L, int index, FromStack)
        : LuaRefBase(L)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(m_L, 1))
            return;
#endif

        lua_pushvalue(m_L, index);
        m_ref = luaL_ref(m_L, LUA_REGISTRYINDEX);
    }

public:
    //=============================================================================================
    /**
     * @brief Create an invalid reference that will be treated as nil.
     *
     * The Lua reference may be assigned later.
     *
     * @param L A Lua state.
     */
    LuaRef(lua_State* L)
        : LuaRefBase(L)
    {
    }

    //=============================================================================================
    /**
     * @brief Push a value onto a Lua stack and return a reference to it.
     *
     * @param L A Lua state.
     * @param v A value to push.
     */
    template <class T>
    LuaRef(lua_State* L, const T& v)
        : LuaRefBase(L)
    {
        std::error_code ec;
        if (! Stack<T>::push(m_L, v, ec))
            return;

        m_ref = luaL_ref(m_L, LUA_REGISTRYINDEX);
    }

    //=============================================================================================
    /**
     * @brief Create a reference to a table item.
     *
     * @param v A table item reference.
     */
    LuaRef(const TableItem& v)
        : LuaRefBase(v.state())
        , m_ref(v.createRef())
    {
    }

    //=============================================================================================
    /**
     * @brief Create a new reference to an existing Lua value.
     *
     * @param other An existing reference.
     */
    LuaRef(const LuaRef& other)
        : LuaRefBase(other.m_L)
        , m_ref(other.createRef())
    {
    }

    //=============================================================================================
    /**
     * @brief Move a reference to an existing Lua value.
     *
     * @param other An existing reference.
     */
    LuaRef(LuaRef&& other)
        : LuaRefBase(other.m_L)
        , m_ref(std::exchange(other.m_ref, LUA_NOREF))
    {
    }

    //=============================================================================================
    /**
     * @brief Destroy a reference.
     *
     * The corresponding Lua registry reference will be released.
     *
     * @note If the state refers to a thread, it is the responsibility of the caller to ensure that the thread still exists when the LuaRef is destroyed.
     */
    ~LuaRef()
    {
        if (m_ref != LUA_NOREF)
            luaL_unref(m_L, LUA_REGISTRYINDEX, m_ref);
    }

    //=============================================================================================
    /**
     * @brief Return a reference to a top Lua stack item.
     *
     * The stack item is not popped.
     *
     * @param L A Lua state.
     *
     * @returns A reference to a value on the top of a Lua stack.
     */
    static LuaRef fromStack(lua_State* L)
    {
        return LuaRef(L, FromStack());
    }

    //=============================================================================================
    /**
     * @brief Return a reference to a Lua stack item with a specified index.
     *
     * The stack item is not removed.
     *
     * @param L     A Lua state.
     * @param index An index in the Lua stack.
     *
     * @returns A reference to a value in a Lua stack.
     */
    static LuaRef fromStack(lua_State* L, int index)
    {
        return LuaRef(L, index, FromStack());
    }

    //=============================================================================================
    /**
     * @brief Create a new empty table on the top of a Lua stack and return a reference to it.
     *
     * @param L A Lua state.
     *
     * @returns A reference to the newly created table.
     *
     * @see luabridge::newTable()
     */
    static LuaRef newTable(lua_State* L)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
            return { L };
#endif

        lua_newtable(L);
        return LuaRef(L, FromStack());
    }

    //=============================================================================================
    /**
     * @brief Return a reference to a named global Lua variable.
     *
     * @param L    A Lua state.
     * @param name The name of a global variable.
     *
     * @returns A reference to the Lua variable.
     *
     * @see luabridge::getGlobal()
     */
    static LuaRef getGlobal(lua_State* L, const char* name)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 1))
            return { L };
#endif

        lua_getglobal(L, name);
        return LuaRef(L, FromStack());
    }

    //=============================================================================================
    /**
     * @brief Indicate whether it is an invalid reference.
     *
     * @returns True if this is an invalid reference, false otherwise.
     */
    bool isValid() const { return m_ref != LUA_NOREF; }

    //=============================================================================================
    /**
     * @brief Assign another LuaRef to this LuaRef.
     *
     * @param rhs A reference to assign from.
     *
     * @returns This reference.
     */
    LuaRef& operator=(const LuaRef& rhs)
    {
        LuaRef ref(rhs);
        swap(ref);
        return *this;
    }

    //=============================================================================================
    /**
     * @brief Move assign another LuaRef to this LuaRef.
     *
     * @param rhs A reference to assign from.
     *
     * @returns This reference.
     */
    LuaRef& operator=(LuaRef&& rhs)
    {
        if (m_ref != LUA_NOREF)
            luaL_unref(m_L, LUA_REGISTRYINDEX, m_ref);

        m_L = rhs.m_L;
        m_ref = std::exchange(rhs.m_ref, LUA_NOREF);
        
        return *this;
    }
    
    //=============================================================================================
    /**
     * @brief Assign a table item reference.
     *
     * @param rhs A table item reference.
     *
     * @returns This reference.
     */
    LuaRef& operator=(const LuaRef::TableItem& rhs)
    {
        LuaRef ref(rhs);
        swap(ref);
        return *this;
    }

    //=============================================================================================
    /**
     * @brief Assign nil to this reference.
     *
     * @returns This reference.
     */
    LuaRef& operator=(const LuaNil&)
    {
        LuaRef ref(m_L);
        swap(ref);
        return *this;
    }

    //=============================================================================================
    /**
     * @brief Assign a different value to this reference.
     *
     * @param rhs A value to assign.
     *
     * @returns This reference.
     */
    template <class T>
    LuaRef& operator=(const T& rhs)
    {
        LuaRef ref(m_L, rhs);
        swap(ref);
        return *this;
    }

    //=============================================================================================
    /**
     * @brief Place the object onto the Lua stack.
     */
    using LuaRefBase::push;

    void push() const
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(m_L, 1))
            return;
#endif

        lua_rawgeti(m_L, LUA_REGISTRYINDEX, m_ref);
    }

    //=============================================================================================
    /**
     * @brief Pop the top of Lua stack and assign the ref to m_ref
     */
    void pop()
    {
        if (m_ref != LUA_NOREF)
            luaL_unref(m_L, LUA_REGISTRYINDEX, m_ref);
        
        m_ref = luaL_ref(m_L, LUA_REGISTRYINDEX);
    }

    //=============================================================================================
    /**
     * @brief Access a table value using a key.
     *
     * This invokes metamethods.
     *
     * @param key A key in the table.
     *
     * @returns A reference to the table item.
     */
    template <class T>
    TableItem operator[](const T& key) const
    {
        std::error_code ec;
        if (! Stack<T>::push(m_L, key, ec))
            return TableItem(m_L, m_ref);

        return TableItem(m_L, m_ref);
    }

    //=============================================================================================
    /**
     * @brief Access a table value using a key.
     *
     * The operation is raw, metamethods are not invoked. The result is passed by value and may not be modified.
     *
     * @param key A key in the table.
     *
     * @returns A reference to the table item.
     */
    template <class T>
    LuaRef rawget(const T& key) const
    {
        StackPop(m_L, 1);

        push(m_L);

        std::error_code ec;
        if (! Stack<T>::push(m_L, key, ec))
            return LuaRef(m_L);

        lua_rawget(m_L, -2);
        return LuaRef(m_L, FromStack());
    }

private:
    void swap(LuaRef& other)
    {
        using std::swap;

        swap(m_L, other.m_L);
        swap(m_ref, other.m_ref);
    }

    int m_ref = LUA_NOREF;
};

//=================================================================================================
/**
 * @brief Stack specialization for `LuaRef`.
 */
template <>
struct Stack<LuaRef>
{
    [[nodiscard]] static bool push(lua_State* L, const LuaRef& v, std::error_code&)
    {
        return v.push(L), true;
    }

    [[nodiscard]] static LuaRef get(lua_State* L, int index)
    {
        return LuaRef::fromStack(L, index);
    }
};

//=================================================================================================
/**
 * @brief Stack specialization for `TableItem`.
 */
template <>
struct Stack<LuaRef::TableItem>
{
    [[nodiscard]] static bool push(lua_State* L, const LuaRef::TableItem& v, std::error_code&)
    {
        return v.push(L), true;
    }
};

//=================================================================================================
/**
 * @brief Create a reference to a new, empty table.
 *
 * This is a syntactic abbreviation for LuaRef::newTable ().
 */
[[nodiscard]] inline LuaRef newTable(lua_State* L)
{
    return LuaRef::newTable(L);
}

//=================================================================================================
/**
 * @brief Create a reference to a value in the global table.
 *
 * This is a syntactic abbreviation for LuaRef::getGlobal ().
 */
[[nodiscard]] inline LuaRef getGlobal(lua_State* L, const char* name)
{
    return LuaRef::getGlobal(L, name);
}

//=================================================================================================
/**
 * @brief C++ like cast syntax.
 */
template <class T>
[[nodiscard]] T cast(const LuaRef& ref)
{
    return ref.cast<T>();
}

} // namespace luabridge

// End File: Source/LuaBridge/detail/LuaRef.h

// Begin File: Source/LuaBridge/detail/Invoke.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2021, Lucio Asnaghi
// SPDX-License-Identifier: MIT

namespace luabridge {

//=================================================================================================
/**
 * @brief Result of a lua invocation.
 */
class LuaResult
{
public:
    /**
     * @brief Get if the result was ok and didn't raise a lua error.
     */
    explicit operator bool() const noexcept
    {
        return !m_ec;
    }

    /**
     * @brief Return if the invocation was ok and didn't raise a lua error.
     */
    bool wasOk() const noexcept
    {
        return !m_ec;
    }

    /**
     * @brief Return if the invocation did raise a lua error.
     */
    bool hasFailed() const noexcept
    {
        return !!m_ec;
    }

    /**
     * @brief Return the error code, if any.
     *
     * In case the invcation didn't raise any lua error, the value returned equals to a
     * default constructed std::error_code.
     */
    std::error_code errorCode() const noexcept
    {
        return m_ec;
    }

    /**
     * @brief Return the error message, if any.
     */
    std::string errorMessage() const noexcept
    {
        if (std::holds_alternative<std::string>(m_data))
        {
            const auto& message = std::get<std::string>(m_data);
            return message.empty() ? m_ec.message() : message;
        }

        return {};
    }

    /**
     * @brief Return the number of return values.
     */
    std::size_t size() const noexcept
    {
        if (std::holds_alternative<std::vector<LuaRef>>(m_data))
            return std::get<std::vector<LuaRef>>(m_data).size();

        return 0;
    }

    /**
     * @brief Get a return value at a specific index.
     */
    LuaRef operator[](std::size_t index) const
    {
        assert(m_ec == std::error_code());

        if (std::holds_alternative<std::vector<LuaRef>>(m_data))
        {
            const auto& values = std::get<std::vector<LuaRef>>(m_data);

            assert(index < values.size());
            return values[index];
        }

        return LuaRef(m_L);
    }

private:
    template <class... Args>
    friend LuaResult call(const LuaRef&, Args&&...);

    static LuaResult errorFromStack(lua_State* L, std::error_code ec)
    {
        auto errorString = lua_tostring(L, -1);
        lua_pop(L, 1);

        return LuaResult(L, ec, errorString ? errorString : ec.message());
    }

    static LuaResult valuesFromStack(lua_State* L, int stackTop)
    {
        std::vector<LuaRef> values;

        const int numReturnedValues = lua_gettop(L) - stackTop;
        if (numReturnedValues > 0)
        {
            values.reserve(numReturnedValues);

            for (int index = numReturnedValues; index > 0; --index)
                values.emplace_back(LuaRef::fromStack(L, -index));

            lua_pop(L, numReturnedValues);
        }

        return LuaResult(L, std::move(values));
    }

    LuaResult(lua_State* L, std::error_code ec, std::string_view errorString)
        : m_L(L)
        , m_ec(ec)
        , m_data(std::string(errorString))
    {
    }

    explicit LuaResult(lua_State* L, std::vector<LuaRef> values) noexcept
        : m_L(L)
        , m_data(std::move(values))
    {
    }

    lua_State* m_L = nullptr;
    std::error_code m_ec;
    std::variant<std::vector<LuaRef>, std::string> m_data;
};

//=================================================================================================
/**
 * @brief Safely call Lua code.
 *
 * These overloads allow Lua code to be called throught lua_pcall.  The return value is provided as
 * a LuaResult which will hold the return values or an error if the call failed.
 *
 * If an error occurs, a LuaException is thrown or if exceptions are disabled the FunctionResult will
 * contain a error code and evaluate false.
 *
 * @note The function might throw a LuaException if the application is compiled with exceptions on
 * and they are enabled globally by calling `enableExceptions` in two cases:
 * - one of the arguments passed cannot be pushed in the stack, for example a unregistered C++ class
 * - the lua invaction calls the panic handler, which is converted to a C++ exception
 *
 * @return A result object.
*/
template <class... Args>
LuaResult call(const LuaRef& object, Args&&... args)
{
    lua_State* L = object.state();
    const int stackTop = lua_gettop(L);

    object.push();

    {
        std::error_code ec;
        auto pushedArgs = detail::push_arguments(L, std::forward_as_tuple(args...), ec);
        if (ec)
        {
            lua_pop(L, static_cast<int>(pushedArgs) + 1);
            return LuaResult(L, ec, ec.message());
        }
    }

    int code = lua_pcall(L, sizeof...(Args), LUA_MULTRET, 0);
    if (code != LUABRIDGE_LUA_OK)
    {
        auto ec = makeErrorCode(ErrorCode::LuaFunctionCallFailed);

#if LUABRIDGE_HAS_EXCEPTIONS
        if (LuaException::areExceptionsEnabled())
            LuaException::raise(LuaException(L, ec));
#else
        return LuaResult::errorFromStack(L, ec);
#endif
    }

    return LuaResult::valuesFromStack(L, stackTop);
}

//=============================================================================================
/**
 * @brief Wrapper for lua_pcall that throws if exceptions are enabled.
 */
inline int pcall(lua_State* L, int nargs = 0, int nresults = 0, int msgh = 0)
{
    const int code = lua_pcall(L, nargs, nresults, msgh);

#if LUABRIDGE_HAS_EXCEPTIONS
    if (code != LUABRIDGE_LUA_OK && LuaException::areExceptionsEnabled())
        LuaException::raise(LuaException(L, makeErrorCode(ErrorCode::LuaFunctionCallFailed)));
#endif

    return code;
}

//=============================================================================================
template <class Impl, class LuaRef>
template <class... Args>
LuaResult LuaRefBase<Impl, LuaRef>::operator()(Args&&... args) const
{
    return call(*this, std::forward<Args>(args)...);
}

} // namespace luabridge

// End File: Source/LuaBridge/detail/Invoke.h

// Begin File: Source/LuaBridge/detail/Iterator.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2018, Dmitry Tarakanov
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// SPDX-License-Identifier: MIT

namespace luabridge {

//=================================================================================================
/**
 * @brief Iterator class to allow table iteration.
 *
 * @see Range class.
 */
class Iterator
{
public:
    explicit Iterator(const LuaRef& table, bool isEnd = false)
        : m_L(table.state())
        , m_table(table)
        , m_key(table.state()) // m_key is nil
        , m_value(table.state()) // m_value is nil
    {
        if (! isEnd)
        {
            next(); // get the first (key, value) pair from table
        }
    }

    /**
     * @brief Return an associated Lua state.
     *
     * @return A Lua state.
     */
    lua_State* state() const noexcept
    {
        return m_L;
    }

    /**
     * @brief Dereference the iterator.
     *
     * @return A key-value pair for a current table entry.
     */
    std::pair<LuaRef, LuaRef> operator*() const
    {
        return std::make_pair(m_key, m_value);
    }

    /**
     * @brief Return the value referred by the iterator.
     *
     * @return A value for the current table entry.
     */
    LuaRef operator->() const
    {
        return m_value;
    }

    /**
     * @brief Compare two iterators.
     *
     * @param rhs Another iterator.
     *
     * @return True if iterators point to the same entry of the same table, false otherwise.
     */
    bool operator!=(const Iterator& rhs) const
    {
        assert(m_L == rhs.m_L);

        return ! m_table.rawequal(rhs.m_table) || ! m_key.rawequal(rhs.m_key);
    }

    /**
     * @brief Move the iterator to the next table entry.
     *
     * @return This iterator.
     */
    Iterator& operator++()
    {
        if (isNil())
        {
            // if the iterator reaches the end, do nothing
            return *this;
        }
        else
        {
            next();
            return *this;
        }
    }

    /**
     * @brief Check if the iterator points after the last table entry.
     *
     * @return True if there are no more table entries to iterate, false otherwise.
     */
    bool isNil() const noexcept
    {
        return m_key.isNil();
    }

    /**
     * @brief Return the key for the current table entry.
     *
     * @return A reference to the entry key.
     */
    LuaRef key() const
    {
        return m_key;
    }

    /**
     * @brief Return the key for the current table entry.
     *
     * @return A reference to the entry value.
     */
    LuaRef value() const
    {
        return m_value;
    }

private:
    // Don't use postfix increment, it is less efficient
    Iterator operator++(int);

    void next()
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(m_L, 2))
        {
            m_key = LuaNil();
            m_value = LuaNil();
            return;
        }
#endif

        m_table.push();
        m_key.push();

        if (lua_next(m_L, -2))
        {
            m_value.pop();
            m_key.pop();
        }
        else
        {
            m_key = LuaNil();
            m_value = LuaNil();
        }

        lua_pop(m_L, 1);
    }

    lua_State* m_L = nullptr;
    LuaRef m_table;
    LuaRef m_key;
    LuaRef m_value;
};

//=================================================================================================
/**
 * @brief Range class taking two table iterators.
 */
class Range
{
public:
    Range(const Iterator& begin, const Iterator& end)
        : m_begin(begin)
        , m_end(end)
    {
    }

    const Iterator& begin() const noexcept
    {
        return m_begin;
    }

    const Iterator& end() const noexcept
    {
        return m_end;
    }

private:
    Iterator m_begin;
    Iterator m_end;
};

//=================================================================================================
/**
 * @brief Return a range for the Lua table reference.
 *
 * @return A range suitable for range-based for statement.
 */
inline Range pairs(const LuaRef& table)
{
    return Range{ Iterator(table, false), Iterator(table, true) };
}

} // namespace luabridge

// End File: Source/LuaBridge/detail/Iterator.h

// Begin File: Source/LuaBridge/detail/Security.h

// https://github.com/vinniefalco/LuaBridge
// Copyright 2021, Lucio Asnaghi
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// SPDX-License-Identifier: MIT

namespace luabridge {

//=================================================================================================
/**
 * @brief Security options.
 */
class Security
{
public:
    static bool hideMetatables() noexcept
    {
        return getSettings().hideMetatables;
    }

    static void setHideMetatables(bool shouldHide) noexcept
    {
        getSettings().hideMetatables = shouldHide;
    }

private:
    struct Settings
    {
        Settings() noexcept
            : hideMetatables(true)
        {
        }

        bool hideMetatables;
    };

    static Settings& getSettings() noexcept
    {
        static Settings settings;
        return settings;
    }
};

//=================================================================================================
/**
 * @brief Get a global value from the lua_State.
 *
 * @note This works on any type specialized by `Stack`, including `LuaRef` and its table proxies.
*/
template <class T>
T getGlobal(lua_State* L, const char* name)
{
    lua_getglobal(L, name);

    auto result = luabridge::Stack<T>::get(L, -1);
    
    lua_pop(L, 1);
    
    return result;
}

//=================================================================================================
/**
 * @brief Set a global value in the lua_State.
 *
 * @note This works on any type specialized by `Stack`, including `LuaRef` and its table proxies.
*/
template <class T>
bool setGlobal(lua_State* L, T&& t, const char* name)
{
    std::error_code ec;
    if (push(L, std::forward<T>(t), ec))
    {
        lua_setglobal(L, name);
        return true;
    }

    return false;
}

//=================================================================================================
/**
 * @brief Change whether or not metatables are hidden (on by default).
 */
inline void setHideMetatables(bool shouldHide) noexcept
{
    Security::setHideMetatables(shouldHide);
}

} // namespace luabridge

// End File: Source/LuaBridge/detail/Security.h

// Begin File: Source/LuaBridge/detail/Namespace.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2019, Dmitry Tarakanov
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// Copyright 2007, Nathan Reed
// SPDX-License-Identifier: MIT

namespace luabridge {
namespace detail {

//=================================================================================================
/**
 * @brief Base for class and namespace registration.
 *
 * Maintains Lua stack in the proper state. Once beginNamespace, beginClass or deriveClass is called the parent object upon its destruction
 * may no longer clear the Lua stack.
 *
 * Then endNamespace or endClass is called, a new parent is created and the child transfers the responsibility for clearing stack to it.
 *
 * So there can be maximum one "active" registrar object.
 */
class Registrar
{
protected:
    Registrar(lua_State* L)
        : L(L)
        , m_stackSize(0)
    {
    }

    Registrar(lua_State* L, int skipStackPops)
        : L(L)
        , m_stackSize(0)
        , m_skipStackPops(skipStackPops)
    {
    }

    Registrar(const Registrar& rhs)
        : L(rhs.L)
        , m_stackSize(std::exchange(rhs.m_stackSize, 0))
        , m_skipStackPops(std::exchange(rhs.m_skipStackPops, 0))
    {
    }

    Registrar& operator=(const Registrar& rhs)
    {
        m_stackSize = rhs.m_stackSize;
        m_skipStackPops = rhs.m_skipStackPops;

        return *this;
    }

    ~Registrar()
    {
        const int popsCount = m_stackSize - m_skipStackPops;
        if (popsCount > 0)
        {
            assert(popsCount <= lua_gettop(L));

            lua_pop(L, popsCount);
        }
    }

    void assertIsActive() const
    {
        if (m_stackSize == 0)
        {
            throw_or_assert<std::logic_error>("Unable to continue registration");
        }
    }

    lua_State* const L = nullptr;
    int mutable m_stackSize = 0;
    int mutable m_skipStackPops = 0;
};

} // namespace detail

//=================================================================================================
/**
 * @brief Provides C++ to Lua registration capabilities.
 *
 * This class is not instantiated directly, call `getGlobalNamespace` to start the registration process.
 */
class Namespace : public detail::Registrar
{
    //=============================================================================================
#if 0
    /**
     * @brief Error reporting.
     *
     * This function looks handy, why aren't we using it?
     */
    static int luaError(lua_State* L, std::string message)
    {
        assert(lua_isstring(L, lua_upvalueindex(1)));
        std::string s;

        // Get information on the caller's caller to format the message,
        // so the error appears to originate from the Lua source.
        lua_Debug ar;
    
        int result = lua_getstack(L, 2, &ar);
        if (result != 0)
        {
            lua_getinfo(L, "Sl", &ar);
            s = ar.short_src;
            if (ar.currentline != -1)
            {
                // poor mans int to string to avoid <strstrream>.
                lua_pushnumber(L, ar.currentline);
                s = s + ":" + lua_tostring(L, -1) + ": ";
                lua_pop(L, 1);
            }
        }

        s = s + message;

        luaL_error(L, "%s", s.c_str());

        return 0;
    }
#endif

    //=============================================================================================
    /**
     * @brief Factored base to reduce template instantiations.
     */
    class ClassBase : public detail::Registrar
    {
    public:
        explicit ClassBase(Namespace& parent)
            : Registrar(parent)
        {
        }

        using Registrar::operator=;

    protected:
        //=========================================================================================
        /**
         * @brief Create the const table.
         */
        void createConstTable(const char* name, bool trueConst = true)
        {
            assert(name != nullptr);

            std::string type_name = std::string(trueConst ? "const " : "") + name;

            // Stack: namespace table (ns)
            lua_newtable(L); // Stack: ns, const table (co)
            lua_pushvalue(L, -1); // Stack: ns, co, co
            lua_setmetatable(L, -2); // co.__metatable = co. Stack: ns, co

            lua_pushstring(L, type_name.c_str());
            lua_rawsetp(L, -2, detail::getTypeKey()); // co [typeKey] = name. Stack: ns, co

            lua_pushcfunction_x(L, &detail::index_metamethod);
            rawsetfield(L, -2, "__index");

            lua_pushcfunction_x(L, &detail::newindex_object_metamethod);
            rawsetfield(L, -2, "__newindex");

            lua_newtable(L);
            lua_rawsetp(L, -2, detail::getPropgetKey());

            if (Security::hideMetatables())
            {
                lua_pushnil(L);
                rawsetfield(L, -2, "__metatable");
            }
        }

        //=========================================================================================
        /**
         * @brief Create the class table.
         *
         * The Lua stack should have the const table on top.
         */
        void createClassTable(const char* name)
        {
            assert(name != nullptr);

            // Stack: namespace table (ns), const table (co)

            // Class table is the same as const table except the propset table
            createConstTable(name, false); // Stack: ns, co, cl

            lua_newtable(L); // Stack: ns, co, cl, propset table (ps)
            lua_rawsetp(L, -2, detail::getPropsetKey()); // cl [propsetKey] = ps. Stack: ns, co, cl

            lua_pushvalue(L, -2); // Stack: ns, co, cl, co
            lua_rawsetp(L, -2, detail::getConstKey()); // cl [constKey] = co. Stack: ns, co, cl

            lua_pushvalue(L, -1); // Stack: ns, co, cl, cl
            lua_rawsetp(L, -3, detail::getClassKey()); // co [classKey] = cl. Stack: ns, co, cl
        }

        //=========================================================================================
        /**
         * @brief Create the static table.
         */
        void createStaticTable(const char* name)
        {
            assert(name != nullptr);

            // Stack: namespace table (ns), const table (co), class table (cl)
            lua_newtable(L); // Stack: ns, co, cl, visible static table (vst)
            lua_newtable(L); // Stack: ns, co, cl, st, static metatable (st)
            lua_pushvalue(L, -1); // Stack: ns, co, cl, vst, st, st
            lua_setmetatable(L, -3); // st.__metatable = mt. Stack: ns, co, cl, vst, st
            lua_insert(L, -2); // Stack: ns, co, cl, st, vst
            rawsetfield(L, -5, name); // ns [name] = vst. Stack: ns, co, cl, st

#if 0
            lua_pushlightuserdata(L, this);
            lua_pushcclosure_x(L, &tostringMetaMethod, 1);
            rawsetfield(L, -2, "__tostring");
#endif

            lua_pushcfunction_x(L, &detail::index_metamethod);
            rawsetfield(L, -2, "__index");

            lua_pushcfunction_x(L, &detail::newindex_static_metamethod);
            rawsetfield(L, -2, "__newindex");

            lua_newtable(L); // Stack: ns, co, cl, st, proget table (pg)
            lua_rawsetp(L, -2, detail::getPropgetKey()); // st [propgetKey] = pg. Stack: ns, co, cl, st

            lua_newtable(L); // Stack: ns, co, cl, st, propset table (ps)
            lua_rawsetp(L, -2, detail::getPropsetKey()); // st [propsetKey] = pg. Stack: ns, co, cl, st

            lua_pushvalue(L, -2); // Stack: ns, co, cl, st, cl
            lua_rawsetp(L, -2, detail::getClassKey()); // st [classKey] = cl. Stack: ns, co, cl, st

            if (Security::hideMetatables())
            {
                lua_pushnil(L);
                rawsetfield(L, -2, "__metatable");
            }
        }

        //=========================================================================================
        /**
         * @brief lua_CFunction to construct a class object wrapped in a container.
         */
        template <class Args, class C>
        static int ctorContainerProxy(lua_State* L)
        {
            using T = typename ContainerTraits<C>::Type;

            T* object = detail::constructor<T, Args>::call(detail::make_arguments_list<Args, 2>(L));

            std::error_code ec;
            if (! detail::UserdataSharedHelper<C, false>::push(L, object, ec))
                luaL_error(L, "%s", ec.message().c_str());

            return 1;
        }

        //=========================================================================================
        /**
         * @brief lua_CFunction to construct a class object in-place in the userdata.
         */
        template <class Args, class T>
        static int ctorPlacementProxy(lua_State* L)
        {
            std::error_code ec;
            detail::UserdataValue<T>* value = detail::UserdataValue<T>::place(L, ec);
            if (! value)
                luaL_error(L, "%s", ec.message().c_str());

            detail::constructor<T, Args>::call(value->getObject(), detail::make_arguments_list<Args, 2>(L));

            value->commit();

            return 1;
        }

        //=========================================================================================
        /**
         * @brief Asserts on stack state.
         */
        void assertStackState() const
        {
            // Stack: const table (co), class table (cl), static table (st)
            assert(lua_istable(L, -3));
            assert(lua_istable(L, -2));
            assert(lua_istable(L, -1));
        }
    };

    //=============================================================================================
    /**
     * @brief Provides a class registration in a lua_State.
     *
     * After construction the Lua stack holds these objects:
     *   -1 static table
     *   -2 class table
     *   -3 const table
     *   -4 enclosing namespace table
     */
    template <class T>
    class Class : public ClassBase
    {
    public:
        //=========================================================================================

        /**
         * @brief Register a new class or add to an existing class registration.
         *
         * @param name   The new class name.
         * @param parent A parent namespace object.
         */
        Class(const char* name, Namespace& parent)
            : ClassBase(parent)
        {
            assert(name != nullptr);
            assert(lua_istable(L, -1)); // Stack: namespace table (ns)

            rawgetfield(L, -1, name); // Stack: ns, static table (st) | nil

            if (lua_isnil(L, -1)) // Stack: ns, nil
            {
                lua_pop(L, 1); // Stack: ns

                createConstTable(name); // Stack: ns, const table (co)
#if !defined(LUABRIDGE_ON_LUAU)
                lua_pushcfunction_x(L, &detail::gc_metamethod<T>); // Stack: ns, co, function
                rawsetfield(L, -2, "__gc"); // co ["__gc"] = function. Stack: ns, co
#endif
                ++m_stackSize;

                createClassTable(name); // Stack: ns, co, class table (cl)
#if !defined(LUABRIDGE_ON_LUAU)
                lua_pushcfunction_x(L, &detail::gc_metamethod<T>); // Stack: ns, co, cl, function
                rawsetfield(L, -2, "__gc"); // cl ["__gc"] = function. Stack: ns, co, cl
#endif
                ++m_stackSize;

                createStaticTable(name); // Stack: ns, co, cl, st
                ++m_stackSize;

                // Map T back to its tables.
                lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st
                lua_rawsetp(L, LUA_REGISTRYINDEX, detail::getStaticRegistryKey<T>()); // Stack: ns, co, cl, st
                lua_pushvalue(L, -2); // Stack: ns, co, cl, st, cl
                lua_rawsetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey<T>()); // Stack: ns, co, cl, st
                lua_pushvalue(L, -3); // Stack: ns, co, cl, st, co
                lua_rawsetp(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey<T>()); // Stack: ns, co, cl, st
            }
            else
            {
                assert(lua_istable(L, -1)); // Stack: ns, st
                ++m_stackSize;

                // Map T back from its stored tables

                lua_rawgetp(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey<T>()); // Stack: ns, st, co
                lua_insert(L, -2); // Stack: ns, co, st
                ++m_stackSize;

                lua_rawgetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey<T>()); // Stack: ns, co, st, cl
                lua_insert(L, -2); // Stack: ns, co, cl, st
                ++m_stackSize;
            }
        }

        //=========================================================================================
        /**
         * @brief Derive a new class.
         *
         * @param name The class name.
         * @param parent A parent namespace object.
         * @param staticKey Key where the class is stored.
        */
        Class(const char* name, Namespace& parent, void const* const staticKey)
            : ClassBase(parent)
        {
            assert(name != nullptr);
            assert(lua_istable(L, -1)); // Stack: namespace table (ns)

            createConstTable(name); // Stack: ns, const table (co)
#if !defined(LUABRIDGE_ON_LUAU)
            lua_pushcfunction_x(L, &detail::gc_metamethod<T>); // Stack: ns, co, function
            rawsetfield(L, -2, "__gc"); // co ["__gc"] = function. Stack: ns, co
#endif
            ++m_stackSize;

            createClassTable(name); // Stack: ns, co, class table (cl)
#if !defined(LUABRIDGE_ON_LUAU)
            lua_pushcfunction_x(L, &detail::gc_metamethod<T>); // Stack: ns, co, cl, function
            rawsetfield(L, -2, "__gc"); // cl ["__gc"] = function. Stack: ns, co, cl
#endif
            ++m_stackSize;

            createStaticTable(name); // Stack: ns, co, cl, st
            ++m_stackSize;

            lua_rawgetp(L, LUA_REGISTRYINDEX, staticKey); // Stack: ns, co, cl, st, parent st (pst) | nil
            if (lua_isnil(L, -1)) // Stack: ns, co, cl, st, nil
            {
                lua_pop(L, 1);

                throw_or_assert<std::logic_error>("Base class is not registered");
                return;
            }

            assert(lua_istable(L, -1)); // Stack: ns, co, cl, st, pst

            lua_rawgetp(L, -1, detail::getClassKey()); // Stack: ns, co, cl, st, pst, parent cl (pcl)
            assert(lua_istable(L, -1));

            lua_rawgetp(L, -1, detail::getConstKey()); // Stack: ns, co, cl, st, pst, pcl, parent co (pco)
            assert(lua_istable(L, -1));

            lua_rawsetp(L, -6, detail::getParentKey()); // co [parentKey] = pco. Stack: ns, co, cl, st, pst, pcl
            lua_rawsetp(L, -4, detail::getParentKey()); // cl [parentKey] = pcl. Stack: ns, co, cl, st, pst
            lua_rawsetp(L, -2, detail::getParentKey()); // st [parentKey] = pst. Stack: ns, co, cl, st

            lua_pushvalue(L, -1); // Stack: ns, co, cl, st, st
            lua_rawsetp(L, LUA_REGISTRYINDEX, detail::getStaticRegistryKey<T>()); // Stack: ns, co, cl, st
            lua_pushvalue(L, -2); // Stack: ns, co, cl, st, cl
            lua_rawsetp(L, LUA_REGISTRYINDEX, detail::getClassRegistryKey<T>()); // Stack: ns, co, cl, st
            lua_pushvalue(L, -3); // Stack: ns, co, cl, st, co
            lua_rawsetp(L, LUA_REGISTRYINDEX, detail::getConstRegistryKey<T>()); // Stack: ns, co, cl, st
        }

        //=========================================================================================
        /**
         * @brief Continue registration in the enclosing namespace.
         *
         * @returns A parent registration object.
         */
        Namespace endClass()
        {
            assert(m_stackSize > 3);

            m_stackSize -= 3;
            lua_pop(L, 3);
            return Namespace(*this);
        }

        //=========================================================================================
        /**
         * @brief Add or replace a static property.
         *
         * @tparam U The type of the property.
         *
         * @param name The property name.
         * @param value A property value pointer.
         * @param isWritable True for a read-write, false for read-only property.
         *
         * @returns This class registration object.
         */
        template <class U>
        Class<T>& addStaticProperty(const char* name, U* value, bool isWritable = true)
        {
            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            lua_pushlightuserdata(L, value); // Stack: co, cl, st, pointer
            lua_pushcclosure_x(L, &detail::property_getter<U>::call, 1); // Stack: co, cl, st, getter
            detail::add_property_getter(L, name, -2); // Stack: co, cl, st

            if (isWritable)
            {
                lua_pushlightuserdata(L, value); // Stack: co, cl, st, ps, pointer
                lua_pushcclosure_x(L, &detail::property_setter<U>::call, 1); // Stack: co, cl, st, ps, setter
            }
            else
            {
                lua_pushstring(L, name); // Stack: co, cl, st, name
                lua_pushcclosure_x(L, &detail::read_only_error, 1); // Stack: co, cl, st, function
            }

            detail::add_property_setter(L, name, -2); // Stack: co, cl, st

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a static property member.
         *
         * @tparam U The type of the property.
         *
         * @param name The property name.
         * @param get A property getter function pointer.
         * @param set A property setter function pointer, optional, nullable. Omit or pass nullptr for a read-only property.
         *
         * @returns This class registration object.
         */
        template <class U>
        Class<T>& addStaticProperty(const char* name, U (*get)(), void (*set)(U) = nullptr)
        {
            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            lua_pushlightuserdata(L, reinterpret_cast<void*>(get)); // Stack: co, cl, st, function ptr
            lua_pushcclosure_x(L, &detail::invoke_proxy_function<U (*)()>, 1); // Stack: co, cl, st, getter
            detail::add_property_getter(L, name, -2); // Stack: co, cl, st

            if (set != nullptr)
            {
                lua_pushlightuserdata(L, reinterpret_cast<void*>(set)); // Stack: co, cl, st, function ptr
                lua_pushcclosure_x(L, &detail::invoke_proxy_function<void (*)(U)>, 1); // Stack: co, cl, st, setter
            }
            else
            {
                lua_pushstring(L, name); // Stack: co, cl, st, ps, name
                lua_pushcclosure_x(L, &detail::read_only_error, 1); // Stack: co, cl, st, function
            }

            detail::add_property_setter(L, name, -2); // Stack: co, cl, st

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a static member function.
         */
        template <class Function, typename = std::enable_if_t<std::is_pointer_v<Function>>>
        Class<T>& addStaticFunction(const char* name, Function fp)
        {
            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            lua_pushlightuserdata(L, reinterpret_cast<void*>(fp)); // Stack: co, cl, st, function ptr
            lua_pushcclosure_x(L, &detail::invoke_proxy_function<Function>, 1); // co, cl, st, function
            rawsetfield(L, -2, name); // co, cl, st

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a static member function for constructible by std::function.
         */
        template <class Function, typename = std::enable_if_t<!std::is_pointer_v<Function>>>
        Class<T> addStaticFunction(const char* name, Function function)
        {
            using FnType = decltype(function);

            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            lua_newuserdata_aligned<FnType>(L, std::move(function)); // Stack: co, cl, st, function userdata (ud)
            lua_pushcclosure_x(L, &detail::invoke_proxy_functor<FnType>, 1); // Stack: co, cl, st, function
            rawsetfield(L, -2, name); // Stack: co, cl, st

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a lua_CFunction.
         *
         * @param name The name of the function.
         * @param fp   A C-function pointer.
         *
         * @returns This class registration object.
         */
        Class<T>& addStaticFunction(const char* name, lua_CFunction fp)
        {
            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            lua_pushcfunction_x(L, fp); // co, cl, st, function
            rawsetfield(L, -2, name); // co, cl, st

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a property member.
         */
        template <class U, class V>
        Class<T>& addProperty(const char* name, U V::*mp, bool isWritable = true)
        {
            static_assert(std::is_base_of_v<V, T>);

            using MemberPtrType = decltype(mp);

            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            new (lua_newuserdata_x<MemberPtrType>(L, sizeof(MemberPtrType))) MemberPtrType(mp); // Stack: co, cl, st, field ptr
            lua_pushcclosure_x(L, &detail::property_getter<U, T>::call, 1); // Stack: co, cl, st, getter
            lua_pushvalue(L, -1); // Stack: co, cl, st, getter, getter
            detail::add_property_getter(L, name, -5); // Stack: co, cl, st, getter
            detail::add_property_getter(L, name, -3); // Stack: co, cl, st

            if (isWritable)
            {
                new (lua_newuserdata_x<MemberPtrType>(L, sizeof(MemberPtrType))) MemberPtrType(mp); // Stack: co, cl, st, field ptr
                lua_pushcclosure_x(L, &detail::property_setter<U, T>::call, 1); // Stack: co, cl, st, setter
                detail::add_property_setter(L, name, -3); // Stack: co, cl, st
            }

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a property member.
         */
        template <class TG, class TS = TG>
        Class<T>& addProperty(const char* name, TG (T::*get)() const, void (T::*set)(TS) = nullptr)
        {
            using GetType = TG (T::*)() const;
            using SetType = void (T::*)(TS);

            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            new (lua_newuserdata_x<GetType>(L, sizeof(GetType))) GetType(get); // Stack: co, cl, st, funcion ptr
            lua_pushcclosure_x(L, &detail::invoke_const_member_function<GetType, T>, 1); // Stack: co, cl, st, getter
            lua_pushvalue(L, -1); // Stack: co, cl, st, getter, getter
            detail::add_property_getter(L, name, -5); // Stack: co, cl, st, getter
            detail::add_property_getter(L, name, -3); // Stack: co, cl, st

            if (set != nullptr)
            {
                new (lua_newuserdata_x<SetType>(L, sizeof(SetType))) SetType(set); // Stack: co, cl, st, function ptr
                lua_pushcclosure_x(L, &detail::invoke_member_function<SetType, T>, 1); // Stack: co, cl, st, setter
                detail::add_property_setter(L, name, -3); // Stack: co, cl, st
            }

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a property member.
         */
        template <class TG, class TS = TG>
        Class<T>& addProperty(const char* name, TG (T::*get)(lua_State*) const, void (T::*set)(TS, lua_State*) = nullptr)
        {
            using GetType = TG (T::*)(lua_State*) const;
            using SetType = void (T::*)(TS, lua_State*);

            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            new (lua_newuserdata_x<GetType>(L, sizeof(GetType))) GetType(get); // Stack: co, cl, st, funcion ptr
            lua_pushcclosure_x(L, &detail::invoke_const_member_function<GetType, T>, 1); // Stack: co, cl, st, getter
            lua_pushvalue(L, -1); // Stack: co, cl, st, getter, getter
            detail::add_property_getter(L, name, -5); // Stack: co, cl, st, getter
            detail::add_property_getter(L, name, -3); // Stack: co, cl, st

            if (set != nullptr)
            {
                new (lua_newuserdata_x<SetType>(L, sizeof(SetType))) SetType(set); // Stack: co, cl, st, function ptr
                lua_pushcclosure_x(L, &detail::invoke_member_function<SetType, T>, 1); // Stack: co, cl, st, setter
                detail::add_property_setter(L, name, -3); // Stack: co, cl, st
            }

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a property member, by proxy.
         *
         * When a class is closed for modification and does not provide (or cannot provide) the function signatures necessary to implement
         * get or set for a property, this will allow non-member functions act as proxies.
         *
         * Both the get and the set functions require a T const* and T* in the first argument respectively.
         */
        template <class TG, class TS = TG>
        Class<T>& addProperty(const char* name, TG (*get)(T const*), void (*set)(T*, TS) = nullptr)
        {
            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            lua_pushlightuserdata(L, reinterpret_cast<void*>(get)); // Stack: co, cl, st, function ptr
            lua_pushcclosure_x(L, &detail::invoke_proxy_function<TG (*)(const T*)>, 1); // Stack: co, cl, st, getter
            lua_pushvalue(L, -1); // Stack: co, cl, st,, getter, getter
            detail::add_property_getter(L, name, -5); // Stack: co, cl, st, getter
            detail::add_property_getter(L, name, -3); // Stack: co, cl, st

            if (set != nullptr)
            {
                lua_pushlightuserdata( L, reinterpret_cast<void*>(set)); // Stack: co, cl, st, function ptr
                lua_pushcclosure_x(L, &detail::invoke_proxy_function<void (*)(T*, TS)>, 1); // Stack: co, cl, st, setter
                detail::add_property_setter(L, name, -3); // Stack: co, cl, st
            }

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a property member, by proxy C-function.
         *
         * When a class is closed for modification and does not provide (or cannot provide) the function signatures necessary to implement
         * get or set for a property, this will allow non-member functions act as proxies.
         *
         * The object userdata ('this') value is at the index 1.
         * The new value for set function is at the index 2.
         */
        Class<T>& addProperty(const char* name, lua_CFunction get, lua_CFunction set = nullptr)
        {
            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            lua_pushcfunction_x(L, get);
            lua_pushvalue(L, -1); // Stack: co, cl, st,, getter, getter
            detail::add_property_getter(L, name, -5); // Stack: co, cl, st,, getter
            detail::add_property_getter(L, name, -3); // Stack: co, cl, st,

            if (set != nullptr)
            {
                lua_pushcfunction_x(L, set);
                detail::add_property_setter(L, name, -3); // Stack: co, cl, st,
            }

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a property member, by constructible by std::function.
         */
        template <class Getter, typename = std::enable_if_t<!std::is_pointer_v<Getter>>>
        Class<T>& addProperty(const char* name, Getter get)
        {
            using FirstArg = detail::function_argument_t<0, Getter>;
            static_assert(std::is_same_v<std::decay_t<std::remove_pointer_t<FirstArg>>, T>);

            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            using GetType = decltype(get);

            lua_newuserdata_aligned<GetType>(L, std::move(get)); // Stack: co, cl, st, function userdata (ud)
            lua_pushcclosure_x(L, &detail::invoke_proxy_functor<GetType>, 1); // Stack: co, cl, st, getter
            lua_pushvalue(L, -1); // Stack: co, cl, st, getter, getter
            detail::add_property_getter(L, name, -4); // Stack: co, cl, st, getter
            detail::add_property_getter(L, name, -4); // Stack: co, cl, st

            return *this;
        }

        template <class Getter, class Setter, typename = std::enable_if_t<!std::is_pointer_v<Getter> && !std::is_pointer_v<Setter>>>
        Class<T>& addProperty(const char* name, Getter get, Setter set)
        {
            addProperty<Getter>(name, std::move(get));

            using FirstArg = detail::function_argument_t<0, Setter>;
            static_assert(std::is_same_v<std::decay_t<std::remove_pointer_t<FirstArg>>, T>);

            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            using SetType = decltype(set);

            lua_newuserdata_aligned<SetType>(L, std::move(set)); // Stack: co, cl, st, function userdata (ud)
            lua_pushcclosure_x(L, &detail::invoke_proxy_functor<SetType>, 1); // Stack: co, cl, st, setter
            detail::add_property_setter(L, name, -3); // Stack: co, cl, st

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a namespace function by convertible to std::function (capturing lambdas).
         */
        template <class Function, typename = std::enable_if_t<detail::function_arity_v<Function> != 0>>
        Class<T> addFunction(const char* name, Function function)
        {
            using FnType = decltype(function);

            using FirstArg = detail::function_argument_t<0, Function>;
            static_assert(std::is_same_v<std::decay_t<std::remove_pointer_t<FirstArg>>, T>);

            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            if (name == std::string_view("__gc"))
            {
                throw_or_assert<std::logic_error>("__gc metamethod registration is forbidden");
                return *this;
            }

            lua_newuserdata_aligned<FnType>(L, std::move(function)); // Stack: co, cl, st, function userdata (ud)
            lua_pushcclosure_x(L, &detail::invoke_proxy_functor<FnType>, 1); // Stack: co, cl, st, function

            if constexpr (! std::is_const_v<std::remove_reference_t<std::remove_pointer_t<FirstArg>>>)
            {
                rawsetfield(L, -3, name); // Stack: co, cl, st
            }
            else
            {
                lua_pushvalue(L, -1); // Stack: co, cl, st, function, function
                rawsetfield(L, -4, name); // Stack: co, cl, st, function
                rawsetfield(L, -4, name); // Stack: co, cl, st
            }

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a member function.
         */
        template <class U, class ReturnType, class... Params>
        Class<T>& addFunction(const char* name, ReturnType (U::*mf)(Params...))
        {
            static_assert(std::is_base_of_v<U, T>);

            using MemFn = decltype(mf);

            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            if (name == std::string_view("__gc"))
            {
                throw_or_assert<std::logic_error>("__gc metamethod registration is forbidden");
                return *this;
            }

            new (lua_newuserdata_x<MemFn>(L, sizeof(MemFn))) MemFn(mf);
            lua_pushcclosure_x(L, &detail::invoke_member_function<MemFn, T>, 1);
            rawsetfield(L, -3, name); // class table

            return *this;
        }

        template <class U, class ReturnType, class... Params>
        Class<T>& addFunction(const char* name, ReturnType (U::*mf)(Params...) const)
        {
            static_assert(std::is_base_of_v<U, T>);

            using MemFn = decltype(mf);

            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            if (name == std::string_view("__gc"))
            {
                throw_or_assert<std::logic_error>("__gc metamethod registration is forbidden");
                return *this;
            }

            new (lua_newuserdata_x<MemFn>(L, sizeof(MemFn))) MemFn(mf);
            lua_pushcclosure_x(L, &detail::invoke_const_member_function<MemFn, T>, 1);
            lua_pushvalue(L, -1);
            rawsetfield(L, -5, name); // const table
            rawsetfield(L, -3, name); // class table

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a proxy function.
         */
        template <class ReturnType, class... Params>
        Class<T>& addFunction(const char* name, ReturnType (*proxyFn)(T* object, Params...))
        {
            using FnType = decltype(proxyFn);

            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            if (name == std::string_view("__gc"))
            {
                throw_or_assert<std::logic_error>("__gc metamethod registration is forbidden");
                return *this;
            }

            lua_pushlightuserdata(L, reinterpret_cast<void*>(proxyFn)); // Stack: co, cl, st, function ptr
            lua_pushcclosure_x(L, &detail::invoke_proxy_function<FnType>, 1); // Stack: co, cl, st, function
            rawsetfield(L, -3, name); // Stack: co, cl, st

            return *this;
        }

        template <class ReturnType, class... Params>
        Class<T>& addFunction(const char* name, ReturnType (*proxyFn)(const T* object, Params...))
        {
            using FnType = decltype(proxyFn);

            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            if (name == std::string_view("__gc"))
            {
                throw_or_assert<std::logic_error>("__gc metamethod registration is forbidden");
                return *this;
            }

            lua_pushlightuserdata(L, reinterpret_cast<void*>(proxyFn)); // Stack: co, cl, st, function ptr
            lua_pushcclosure_x(L, &detail::invoke_proxy_function<FnType>, 1); // Stack: co, cl, st, function
            lua_pushvalue(L, -1); // Stack: co, cl, st, function, function
            rawsetfield(L, -4, name); // Stack: co, cl, st, function
            rawsetfield(L, -4, name); // Stack: co, cl, st

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a member lua_CFunction.
         */
        template <class U>
        Class<T>& addFunction(const char* name, int (U::*mfp)(lua_State*))
        {
            static_assert(std::is_base_of_v<U, T>);

            using F = decltype(mfp);

            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            if (name == std::string_view("__gc"))
            {
                throw_or_assert<std::logic_error>("__gc metamethod registration is forbidden");
                return *this;
            }

            new (lua_newuserdata_x<F>(L, sizeof(mfp))) F(mfp); // Stack: co, cl, st, function ptr
            lua_pushcclosure_x(L, &detail::invoke_member_cfunction<T>, 1); // Stack: co, cl, st, function
            rawsetfield(L, -3, name); // Stack: co, cl, st

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a const member lua_CFunction.
         */
        template <class U>
        Class<T>& addFunction(const char* name, int (U::*mfp)(lua_State*) const)
        {
            static_assert(std::is_base_of_v<U, T>);

            using F = decltype(mfp);

            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            if (name == std::string_view("__gc"))
            {
                throw_or_assert<std::logic_error>("__gc metamethod registration is forbidden");
                return *this;
            }

            new (lua_newuserdata_x<F>(L, sizeof(mfp))) F(mfp);
            lua_pushcclosure_x(L, &detail::invoke_const_member_cfunction<T>, 1);
            lua_pushvalue(L, -1); // Stack: co, cl, st, function, function
            rawsetfield(L, -4, name); // Stack: co, cl, st, function
            rawsetfield(L, -4, name); // Stack: co, cl, st

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a free lua_CFunction that works as a member.
         *
         * This object is at top of the stack, then all other arguments.
         */
        Class<T>& addFunction(const char* name, lua_CFunction fp)
        {
            assert(name != nullptr);
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            if (name == std::string_view("__gc"))
            {
                throw_or_assert<std::logic_error>("__gc metamethod registration is forbidden");
                return *this;
            }

            lua_pushcfunction_x(L, fp); // Stack: co, cl, st, function
            rawsetfield(L, -3, name); // Stack: co, cl, st

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a primary Constructor.
         *
         * The primary Constructor is invoked when calling the class type table like a function.
         *
         * The template parameter should be a function pointer type that matches the desired Constructor (since you can't take the
         * address of a Constructor and pass it as an argument).
         */
        template <class MemFn, class C>
        Class<T>& addConstructor()
        {
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            lua_pushcclosure_x(L, &ctorContainerProxy<detail::function_arguments_t<MemFn>, C>, 0);
            rawsetfield(L, -2, "__call");

            return *this;
        }

        template <class MemFn>
        Class<T>& addConstructor()
        {
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            lua_pushcclosure_x(L, &ctorPlacementProxy<detail::function_arguments_t<MemFn>, T>, 0);
            rawsetfield(L, -2, "__call");

            return *this;
        }

        //=========================================================================================
        /**
         * @brief Add or replace a factory.
         *
         * The primary Constructor is invoked when calling the class type table like a function.
         *
         * The template parameter should be a function pointer type that matches the desired Constructor (since you can't take the
         * address of a Constructor and pass it as an argument).
         */
        template <class Function>
        Class<T> addConstructor(Function function)
        {
            assertStackState(); // Stack: const table (co), class table (cl), static table (st)

            auto factory = [function = std::move(function)](lua_State* L) -> T*
            {
                std::error_code ec;
                detail::UserdataValue<T>* value = detail::UserdataValue<T>::place(L, ec);
                if (! value)
                    luaL_error(L, "%s", ec.message().c_str());

                using FnTraits = detail::function_traits<Function>;
                using FnArgs = detail::remove_first_type_t<typename FnTraits::argument_types>;

                T* obj = detail::factory<T>::call(value->getObject(), function, detail::make_arguments_list<FnArgs, 2>(L));

                value->commit();

                return obj;
            };

            using FactoryFnType = decltype(factory);

            lua_newuserdata_aligned<FactoryFnType>(L, std::move(factory)); // Stack: co, cl, st, function userdata (ud)
            lua_pushcclosure_x(L, &detail::invoke_proxy_functor<FactoryFnType>, 1); // Stack: co, cl, st, function
            rawsetfield(L, -2, "__call"); // Stack: co, cl, st

            return *this;
        }
    };

private:
    struct FromStack {};

    //=============================================================================================
    /**
     * @brief Open the global namespace for registrations.
     *
     * @param L A Lua state.
     */
    explicit Namespace(lua_State* L)
        : Registrar(L)
    {
        lua_getglobal(L, "_G");

        ++m_stackSize;
    }

    //=============================================================================================
    /**
     * @brief Open the a namespace for registrations from a table on top of the stack.
     *
     * @param L A Lua state.
     */
    Namespace(lua_State* L, FromStack)
        : Registrar(L, 1)
    {
        assert(lua_istable(L, -1));

        {
            lua_pushvalue(L, -1); // Stack: ns, mt

            // ns.__metatable = ns
            lua_setmetatable(L, -2); // Stack: ns, mt

            // ns.__index = index_metamethod
            lua_pushcfunction_x(L, &detail::index_metamethod);
            rawsetfield(L, -2, "__index"); // Stack: ns

            lua_newtable(L); // Stack: ns, mt, propget table (pg)
            lua_rawsetp(L, -2, detail::getPropgetKey()); // ns [propgetKey] = pg. Stack: ns

            lua_newtable(L); // Stack: ns, mt, propset table (ps)
            lua_rawsetp(L, -2, detail::getPropsetKey()); // ns [propsetKey] = ps. Stack: ns
        }

        ++m_stackSize;
    }

    //=============================================================================================
    /**
     * @brief Open a namespace for registrations.
     *
     * The namespace is created if it doesn't already exist.
     *
     * @param name The namespace name.
     * @param parent The parent namespace object.
     *
     * @pre The parent namespace is at the top of the Lua stack.
     */
    Namespace(const char* name, Namespace& parent)
        : Registrar(parent)
    {
        assert(name != nullptr);
        assert(lua_istable(L, -1)); // Stack: parent namespace (pns)

        rawgetfield(L, -1, name); // Stack: pns, namespace (ns) | nil

        if (lua_isnil(L, -1)) // Stack: pns, nil
        {
            lua_pop(L, 1); // Stack: pns

            lua_newtable(L); // Stack: pns, ns
            lua_pushvalue(L, -1); // Stack: pns, ns, mt

            // ns.__metatable = ns
            lua_setmetatable(L, -2); // Stack: pns, ns

            // ns.__index = index_metamethod
            lua_pushcfunction_x(L, &detail::index_metamethod);
            rawsetfield(L, -2, "__index"); // Stack: pns, ns

            // ns.__newindex = newindex_static_metamethod
            lua_pushcfunction_x(L, &detail::newindex_static_metamethod);
            rawsetfield(L, -2, "__newindex"); // Stack: pns, ns

            lua_newtable(L); // Stack: pns, ns, propget table (pg)
            lua_rawsetp(L, -2, detail::getPropgetKey()); // ns [propgetKey] = pg. Stack: pns, ns

            lua_newtable(L); // Stack: pns, ns, propset table (ps)
            lua_rawsetp(L, -2, detail::getPropsetKey()); // ns [propsetKey] = ps. Stack: pns, ns

            // pns [name] = ns
            lua_pushvalue(L, -1); // Stack: pns, ns, ns
            rawsetfield(L, -3, name); // Stack: pns, ns
        }

        ++m_stackSize;
    }

    //=============================================================================================
    /**
     * @brief Close the class and continue the namespace registrations.
     *
     * @param child A child class registration object.
     */
    explicit Namespace(ClassBase& child)
        : Registrar(child)
    {
    }

    using Registrar::operator=;

public:
    //=============================================================================================
    /**
     * @brief Retrieve the global namespace.
     *
     * It is recommended to put your namespace inside the global namespace, and then add your classes and functions to it, rather than
     * adding many classes and functions directly to the global namespace.
     *
     * @param L A Lua state.
     *
     * @returns A namespace registration object.
     */
    static Namespace getGlobalNamespace(lua_State* L)
    {
        return Namespace(L);
    }

    /**
     * @brief Retrieve the namespace on top of the stack.
     *
     * You should have a table on top of the stack before calling this function. It will then use the table there as destination for registrations.
     *
     * @param L A Lua state.
     *
     * @returns A namespace registration object.
     */
    static Namespace getNamespaceFromStack(lua_State* L)
    {
        return Namespace(L, FromStack{});
    }

    //=============================================================================================
    /**
     * @brief Open a new or existing namespace for registrations.
     *
     * @param name The namespace name.
     *
     * @returns A namespace registration object.
     */
    Namespace beginNamespace(const char* name)
    {
        assertIsActive();
        return Namespace(name, *this);
    }

    //=============================================================================================
    /**
     * @brief Continue namespace registration in the parent.
     *
     * Do not use this on the global namespace.
     *
     * @returns A parent namespace registration object.
     */
    Namespace endNamespace()
    {
        if (m_stackSize == 1)
        {
            throw_or_assert<std::logic_error>("endNamespace() called on global namespace");

            return Namespace(*this);
        }

        assert(m_stackSize > 1);
        --m_stackSize;
        lua_pop(L, 1);
        return Namespace(*this);
    }

    //=============================================================================================
    /**
     * @brief Add or replace a variable.
     *
     * @param name The property name.
     * @param value A value pointer.
     *
     * @returns This namespace registration object.
     */
    template <class T>
    Namespace& addVariable(const char* name, const T& value)
    {
        if (m_stackSize == 1)
        {
            throw_or_assert<std::logic_error>("addVariable() called on global namespace");

            return *this;
        }

        assert(name != nullptr);
        assert(lua_istable(L, -1)); // Stack: namespace table (ns)

        std::error_code ec;
        if constexpr (std::is_enum_v<T>)
        {
            using U = std::underlying_type_t<T>;
            
            if (! Stack<U>::push(L, static_cast<U>(value), ec))
                luaL_error(L, "%s", ec.message().c_str());
        }
        else
        {
            if (! Stack<T>::push(L, value, ec))
                luaL_error(L, "%s", ec.message().c_str());
        }

        rawsetfield(L, -2, name); // Stack: ns

        return *this;
    }

    //=============================================================================================
    /**
     * @brief Add or replace a property.
     *
     * @param name The property name.
     * @param value A value pointer.
     * @param isWritable True for a read-write, false for read-only property.
     *
     * @returns This namespace registration object.
     */
    template <class T>
    Namespace& addProperty(const char* name, T* value, bool isWritable = true)
    {
        if (m_stackSize == 1)
        {
            throw_or_assert<std::logic_error>("addProperty() called on global namespace");

            return *this;
        }

        assert(name != nullptr);
        assert(lua_istable(L, -1)); // Stack: namespace table (ns)

        lua_pushlightuserdata(L, value); // Stack: ns, pointer
        lua_pushcclosure_x(L, &detail::property_getter<T>::call, 1); // Stack: ns, getter
        detail::add_property_getter(L, name, -2); // Stack: ns

        if (isWritable)
        {
            lua_pushlightuserdata(L, value); // Stack: ns, pointer
            lua_pushcclosure_x(L, &detail::property_setter<T>::call, 1); // Stack: ns, setter
        }
        else
        {
            lua_pushstring(L, name); // Stack: ns, ps, name
            lua_pushcclosure_x(L, &detail::read_only_error, 1); // Stack: ns, function
        }

        detail::add_property_setter(L, name, -2); // Stack: ns

        return *this;
    }

    //=============================================================================================
    /**
     * @brief Add or replace a property.
     *
     * If the set function is omitted or null, the property is read-only.
     *
     * @param name The property name.
     * @param get  A pointer to a property getter function.
     * @param set  A pointer to a property setter function, optional.
     *
     * @returns This namespace registration object.
     */
    template <class TG, class TS = TG>
    Namespace& addProperty(const char* name, TG (*get)(), void (*set)(TS) = nullptr)
    {
        if (m_stackSize == 1)
        {
            throw_or_assert<std::logic_error>("addProperty() called on global namespace");

            return *this;
        }

        assert(name != nullptr);
        assert(lua_istable(L, -1)); // Stack: namespace table (ns)

        lua_pushlightuserdata(L, reinterpret_cast<void*>(get)); // Stack: ns, function ptr
        lua_pushcclosure_x(L, &detail::invoke_proxy_function<TG (*)()>, 1); // Stack: ns, getter
        detail::add_property_getter(L, name, -2);

        if (set != nullptr)
        {
            lua_pushlightuserdata(L, reinterpret_cast<void*>(set)); // Stack: ns, function ptr
            lua_pushcclosure_x(L, &detail::invoke_proxy_function<void (*)(TS)>, 1);
        }
        else
        {
            lua_pushstring(L, name);
            lua_pushcclosure_x(L, &detail::read_only_error, 1);
        }

        detail::add_property_setter(L, name, -2);

        return *this;
    }

    //=============================================================================================
    /**
     * @brief Add or replace a readonly property.
     *
     * @param name The property name.
     * @param get  A pointer to a property getter function.
     *
     * @returns This namespace registration object.
     */
    template <class Getter>
    Namespace& addProperty(const char* name, Getter get)
    {
        using GetType = decltype(get);

        assert(name != nullptr);
        assert(lua_istable(L, -1)); // Stack: namespace table (ns)

        lua_newuserdata_aligned<GetType>(L, std::move(get)); // Stack: ns, function userdata (ud)
        lua_pushcclosure_x(L, &detail::invoke_proxy_functor<GetType>, 1); // Stack: ns, ud, getter
        detail::add_property_getter(L, name, -2); // Stack: ns, ud, getter

        lua_pushstring(L, name); // Stack: ns, name
        lua_pushcclosure_x(L, &detail::read_only_error, 1); // Stack: ns, name, function
        detail::add_property_setter(L, name, -2); // Stack: ns

        return *this;
    }

    /**
     * @brief Add or replace a mutable property.
     *
     * @param name The property name.
     * @param get  A pointer to a property getter function.
     * @param set  A pointer to a property setter function.
     *
     * @returns This namespace registration object.
     */
    template <class Getter, class Setter>
    Namespace& addProperty(const char* name, Getter get, Setter set)
    {
        assert(name != nullptr);
        assert(lua_istable(L, -1)); // Stack: namespace table (ns)

        addProperty<Getter>(name, std::move(get));

        using SetType = decltype(set);

        lua_newuserdata_aligned<SetType>(L, std::move(set)); // Stack: ns, function userdata (ud)
        lua_pushcclosure_x(L, &detail::invoke_proxy_functor<SetType>, 1); // Stack: ns, ud, getter
        detail::add_property_setter(L, name, -2); // Stack: ns, ud, getter

        return *this;
    }

    //=============================================================================================
    /**
     * @brief Add or replace a property.
     *
     * If the set function is omitted or null, the property is read-only.
     *
     * @param name The property name.
     * @param get  A pointer to a property getter function.
     * @param set  A pointer to a property setter function, optional.
     *
     * @returns This namespace registration object.
     */
    Namespace& addProperty(const char* name, lua_CFunction get, lua_CFunction set = nullptr)
    {
        assert(name != nullptr);
        assert(lua_istable(L, -1)); // Stack: namespace table (ns)

        lua_pushcfunction_x(L, get); // Stack: ns, getter
        detail::add_property_getter(L, name, -2); // Stack: ns

        if (set != nullptr)
        {
            lua_pushcfunction_x(L, set); // Stack: ns, setter
            detail::add_property_setter(L, name, -2); // Stack: ns
        }
        else
        {
            lua_pushstring(L, name); // Stack: ns, name
            lua_pushcclosure_x(L, &detail::read_only_error, 1); // Stack: ns, name, function
            detail::add_property_setter(L, name, -2); // Stack: ns
        }

        return *this;
    }

    //=============================================================================================
    /**
     * @brief Add or replace a namespace function by convertible to std::function.
     */
    template <class Function>
    Namespace& addFunction(const char* name, Function function)
    {
        using FnType = decltype(function);

        assert(name != nullptr);
        assert(lua_istable(L, -1)); // Stack: namespace table (ns)

        lua_newuserdata_aligned<FnType>(L, std::move(function)); // Stack: ns, function userdata (ud)
        lua_pushcclosure_x(L, &detail::invoke_proxy_functor<FnType>, 1); // Stack: ns, function
        rawsetfield(L, -2, name); // Stack: ns

        return *this;
    }

    //=============================================================================================
    /**
     * @brief Add or replace a free function.
     */
    template <class ReturnType, class... Params>
    Namespace& addFunction(const char* name, ReturnType (*fp)(Params...))
    {
        using FnType = decltype(fp);

        assert(name != nullptr);
        assert(lua_istable(L, -1)); // Stack: namespace table (ns)

        lua_pushlightuserdata(L, reinterpret_cast<void*>(fp)); // Stack: ns, function ptr
        lua_pushcclosure_x(L, &detail::invoke_proxy_function<FnType>, 1); // Stack: ns, function
        rawsetfield(L, -2, name); // Stack: ns

        return *this;
    }

    //=============================================================================================
    /**
     * @brief Add or replace a lua_CFunction.
     *
     * @param name The function name.
     * @param fp   A C-function pointer.
     *
     * @returns This namespace registration object.
     */
    Namespace& addFunction(const char* name, lua_CFunction fp)
    {
        assert(name != nullptr);
        assert(lua_istable(L, -1)); // Stack: namespace table (ns)

        lua_pushcfunction_x(L, fp); // Stack: ns, function
        rawsetfield(L, -2, name); // Stack: ns

        return *this;
    }

    //=============================================================================================
    /**
     * @brief Open a new or existing class for registrations.
     *
     * @param name The class name.
     *
     * @returns A class registration object.
     */
    template <class T>
    Class<T> beginClass(const char* name)
    {
        assertIsActive();
        return Class<T>(name, *this);
    }

    //=============================================================================================
    /**
     * @brief Derive a new class for registrations.
     *
     * Call deriveClass() only once. To continue registrations for the class later, use beginClass().
     *
     * @param name The class name.
     *
     * @returns A class registration object.
     */
    template <class Derived, class Base>
    Class<Derived> deriveClass(const char* name)
    {
        assertIsActive();
        return Class<Derived>(name, *this, detail::getStaticRegistryKey<Base>());
    }
};

//=================================================================================================
/**
 * @brief Retrieve the global namespace.
 *
 * It is recommended to put your namespace inside the global namespace, and then add your classes and functions to it, rather than adding
 * many classes and functions directly to the global namespace.
 *
 * @param L A Lua state.
 *
 * @returns A namespace registration object.
 */
inline Namespace getGlobalNamespace(lua_State* L)
{
    return Namespace::getGlobalNamespace(L);
}

//=================================================================================================
/**
 * @brief Retrieve the namespace on top of the stack.
 *
 * You should have a table on top of the stack before calling this function. It will then use the table there as destination for registrations.
 *
 * @param L A Lua state.
 *
 * @returns A namespace registration object.
 */
inline Namespace getNamespaceFromStack(lua_State* L)
{
    return Namespace::getNamespaceFromStack(L);
}

} // namespace luabridge

// End File: Source/LuaBridge/detail/Namespace.h

// Begin File: Source/LuaBridge/LuaBridge.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2020, Dmitry Tarakanov
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// Copyright 2007, Nathan Reed
// SPDX-License-Identifier: MIT

// All #include dependencies are listed here
// instead of in the individual header files.

#define LUABRIDGE_MAJOR_VERSION 3
#define LUABRIDGE_MINOR_VERSION 1
#define LUABRIDGE_VERSION 301


// End File: Source/LuaBridge/LuaBridge.h

// Begin File: Source/LuaBridge/List.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2020, Dmitry Tarakanov
// SPDX-License-Identifier: MIT

namespace luabridge {

//=================================================================================================
/**
 * @brief Stack specialization for `std::array`.
 */
template <class T>
struct Stack<std::list<T>>
{
    using Type = std::list<T>;
    
    [[nodiscard]] static bool push(lua_State* L, const Type& list, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 3))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        const int initialStackSize = lua_gettop(L);

        lua_createtable(L, static_cast<int>(list.size()), 0);

        auto it = list.cbegin();
        for (lua_Integer tableIndex = 1; it != list.cend(); ++tableIndex, ++it)
        {
            lua_pushinteger(L, tableIndex);

            std::error_code errorCode;
            if (! Stack<T>::push(L, *it, errorCode))
            {
                ec = errorCode;
                lua_pop(L, lua_gettop(L) - initialStackSize);
                return false;
            }

            lua_settable(L, -3);
        }
        
        return true;
    }

    [[nodiscard]] static Type get(lua_State* L, int index)
    {
        if (!lua_istable(L, index))
            luaL_error(L, "#%d argument must be a table", index);

        Type list;

        int absIndex = lua_absindex(L, index);
        lua_pushnil(L);

        while (lua_next(L, absIndex) != 0)
        {
            list.emplace_back(Stack<T>::get(L, -1));
            lua_pop(L, 1);
        }

        return list;
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_istable(L, index);
    }
};

} // namespace luabridge

// End File: Source/LuaBridge/List.h

// Begin File: Source/LuaBridge/Array.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2020, Dmitry Tarakanov
// SPDX-License-Identifier: MIT

namespace luabridge {

//=================================================================================================
/**
 * @brief Stack specialization for `std::array`.
 */
template <class T, std::size_t Size>
struct Stack<std::array<T, Size>>
{
    using Type = std::array<T, Size>;

    [[nodiscard]] static bool push(lua_State* L, const Type& array, std::error_code& ec)
    {
#if LUABRIDGE_SAFE_STACK_CHECKS
        if (! lua_checkstack(L, 3))
        {
            ec = makeErrorCode(ErrorCode::LuaStackOverflow);
            return false;
        }
#endif

        const int initialStackSize = lua_gettop(L);
        
        lua_createtable(L, static_cast<int>(Size), 0);

        for (std::size_t i = 0; i < Size; ++i)
        {
            lua_pushinteger(L, static_cast<lua_Integer>(i + 1));

            std::error_code errorCode;
            bool result = Stack<T>::push(L, array[i], errorCode);
            if (!result)
            {
                ec = errorCode;
                lua_pop(L, lua_gettop(L) - initialStackSize);
                return false;
            }

            lua_settable(L, -3);
        }
        
        return true;
    }

    [[nodiscard]] static Type get(lua_State* L, int index)
    {
        if (!lua_istable(L, index))
            luaL_error(L, "#%d argment must be a table", index);

        if (get_length(L, index) != Size)
            luaL_error(L, "table size should be %d but is %d", static_cast<int>(Size), get_length(L, index));

        Type array;

        int absIndex = lua_absindex(L, index);
        lua_pushnil(L);

        int arrayIndex = 0;
        while (lua_next(L, absIndex) != 0)
        {
            array[arrayIndex++] = Stack<T>::get(L, -1);
            lua_pop(L, 1);
        }

        return array;
    }

    [[nodiscard]] static bool isInstance(lua_State* L, int index)
    {
        return lua_istable(L, index) && get_length(L, index) == Size;
    }
};

} // namespace luabridge

// End File: Source/LuaBridge/Array.h

// Begin File: Source/LuaBridge/detail/Dump.h

// https://github.com/kunitoki/LuaBridge3
// Copyright 2020, Lucio Asnaghi
// Copyright 2019, Dmitry Tarakanov
// Copyright 2012, Vinnie Falco <vinnie.falco@gmail.com>
// Copyright 2007, Nathan Reed
// SPDX-License-Identifier: MIT

namespace luabridge {
namespace debug {

inline void putIndent(std::ostream& stream, unsigned level)
{
    for (unsigned i = 0; i < level; ++i)
    {
        stream << "  ";
    }
}

inline void dumpTable(lua_State* L, int index, std::ostream& stream, unsigned level);

inline void dumpValue(lua_State* L, int index, std::ostream& stream, unsigned level = 0)
{
    const int type = lua_type(L, index);
    switch (type)
    {
    case LUA_TNIL:
        stream << "nil";
        break;

    case LUA_TBOOLEAN:
        stream << (lua_toboolean(L, index) ? "true" : "false");
        break;

    case LUA_TNUMBER:
        stream << lua_tonumber(L, index);
        break;

    case LUA_TSTRING:
        stream << '"' << lua_tostring(L, index) << '"';
        break;

    case LUA_TFUNCTION:
        if (lua_iscfunction(L, index))
        {
            stream << "cfunction@" << lua_topointer(L, index);
        }
        else
        {
            stream << "function@" << lua_topointer(L, index);
        }
        break;

    case LUA_TTHREAD:
        stream << "thread@" << lua_tothread(L, index);
        break;

    case LUA_TLIGHTUSERDATA:
        stream << "lightuserdata@" << lua_touserdata(L, index);
        break;

    case LUA_TTABLE:
        dumpTable(L, index, stream, level);
        break;

    case LUA_TUSERDATA:
        stream << "userdata@" << lua_touserdata(L, index);
        break;

    default:
        stream << lua_typename(L, type);
        ;
        break;
    }
}

inline void dumpTable(lua_State* L, int index, std::ostream& stream, unsigned level = 0)
{
    stream << "table@" << lua_topointer(L, index);

    if (level > 0)
    {
        return;
    }

    index = lua_absindex(L, index);
    stream << " {";
    lua_pushnil(L); // Initial key
    while (lua_next(L, index))
    {
        stream << "\n";
        putIndent(stream, level + 1);
        dumpValue(L, -2, stream, level + 1); // Key
        stream << ": ";
        dumpValue(L, -1, stream, level + 1); // Value
        lua_pop(L, 1); // Value
    }
    putIndent(stream, level);
    stream << "\n}";
}

inline void dumpState(lua_State* L, std::ostream& stream = std::cerr)
{
    int top = lua_gettop(L);
    for (int i = 1; i <= top; ++i)
    {
        stream << "stack #" << i << ": ";
        dumpValue(L, i, stream, 0);
        stream << "\n";
    }
}

} // namespace debug
} // namespace luabridge

// End File: Source/LuaBridge/detail/Dump.h

// clang-format on

