#pragma once

#include <cstddef>
#include <cstdint>
#include <utility>
#include "external/il2cpp/il2cpp-class-internals.h"
#include "adapters/il2cpp/Il2CppObjectAccess.h"
#include "adapters/il2cpp/Il2CppResolver.h"
#include "adapters/il2cpp/ManagedRuntime.h"

namespace ta::adapters::il2cpp
{
/**
 * Represents an instance of an il2cpp object and provides interfaces for it
 */
class Il2CppObjectInstance
{
public:
    /**
     * Should be used in most cases, automatically sets the class
     * @param instance object instance
     */
    explicit Il2CppObjectInstance(Il2CppObject* instance)
    {
        this->clazz_ = instance ? instance->klass : nullptr;
        this->instance_ = instance;
    }

    /**
     * Explicitly declare the class
     * @param clazz il2cpp class
     * @param instance object instance
     */
    Il2CppObjectInstance(const Il2CppClass* clazz, Il2CppObject* instance)
    {
        this->clazz_ = clazz;
        this->instance_ = instance;
    }

    /**
     * Resolve the class
     * @param image il2cpp image
     * @param namespaze namespce
     * @param name class name
     * @param instance object instance
     */
    Il2CppObjectInstance(const Il2CppImage* image, const char* namespaze, const char* name,
                         Il2CppObject* instance)
    {
        clazz_ = ta::adapters::il2cpp::resolve_class(image, namespaze, name);
        instance_ = instance;
    }

    template <typename T>
    bool set_field(const char* name, T value)
    {
        return instance_ && clazz_ && name &&
               ManagedRuntime::write_field(instance_, name, std::move(value));
    }

    template <typename T>
    [[nodiscard]] bool try_get_field(const char* name, T& value) const
    {
        if (!instance_ || !clazz_ || !name)
            return false;
        const auto result = ManagedRuntime::read_field<T>(instance_, name);
        if (!result)
            return false;
        value = result.value();
        return true;
    }

    template <typename T>
    T get_method(const char* name, const int args)
    {
        return ManagedRuntime::function<T>(clazz_, name ? name : "", args);
    }

    Il2CppObject* get_instance() const
    {
        return instance_;
    }

private:
    const Il2CppClass* clazz_;
    Il2CppObject* instance_;
};
}
