#pragma once
#include <ISXDK.h>
#include <optional>
#include <vector>
#include <sstream>

#include "ISXMr.h"
#include "Logger.h"

template <typename T>
std::string toString(const T &value)
{
    std::ostringstream oss;
    oss << value;
    return oss.str();
}

class LSObject
{
public:
    explicit LSObject(const optional<LSOBJECT> &obj) : lsObject(obj)
    {
    }

    ~LSObject()
    {
    }

    template <typename... Args>
    optional<LSOBJECT> GetMember(const std::string &memberName, Args... args)
    {
        if (!this->lsObject.has_value())
        {
            return nullopt;
        }

        std::vector<char *> argv = ConvertArgsToVector(args...);

        try
        {
            LSOBJECT response;

            if (const auto result = this->lsObject->Type->GetMemberEx(
                    this->lsObject->GetObjectData(),
                    const_cast<char *>(memberName.c_str()),
                    static_cast<int>(argv.size()),
                    argv.data(),
                    response);
                !result)
            {
                logd << "GetMemberEx failed on member " << memberName
                     << " for type " << this->lsObject->Type->GetName()
                     << endl;
            }
            else
            {
                return response;
            }
        }
        catch (...)
        {
            loge << "GetMemberEx failed" << endl;
        }

        return nullopt;
    }

    template <typename... Args>
    void CallMethod(const std::string &methodName, Args... args)
    {
        if (!this->lsObject.has_value())
        {
            return;
        }
        std::vector<char *> argv = ConvertArgsToVector(args...);

        this->lsObject->Type->GetMethodEx(
            this->lsObject->GetObjectData(),
            const_cast<char *>(methodName.c_str()),
            static_cast<int>(argv.size()),
            argv.data());
    }

    [[nodiscard]] bool IsValid() const
    {
        return this->lsObject.has_value();
    }

    [[nodiscard]] string AsString() const
    {
        if (this->lsObject.has_value())
        {
            return this->lsObject->CharPtr;
        }
        return {};
    }

    [[nodiscard]] int AsInt() const
    {
        if (this->lsObject.has_value())
        {
            return this->lsObject->Int;
        }

        return {};
    }

    [[nodiscard]] float AsFloat() const
    {
        if (this->lsObject.has_value())
        {
            return this->lsObject->Float;
        }

        return {};
    }

    static optional<shared_ptr<LSObject>> FromDataParse(const std::string &dataToParse)
    {
        // auto type = pISInterface->FindLSType(const_cast<char *>("MRBotController"));

        if (LSOBJECT rawObject; pISInterface->DataParse(dataToParse.c_str(), rawObject))
        {
            logd << "Got object from DataParse" << endl;
            return make_shared<LSObject>(LSObject(rawObject));
        }

        logd << "DataParse failed on " << dataToParse << endl;

        return nullopt;
    }

private:
    optional<LSOBJECT> lsObject;

    template <typename... Args>
    std::vector<char *> ConvertArgsToVector(Args... args)
    {
        // Use a fold expression to convert each argument to a string
        const std::vector<std::string> arguments = {toString(args)...};

        const size_t argc = arguments.size();
        std::vector<char *> argv;

        for (auto &arg : arguments)
        {
            argv.push_back(const_cast<char *>(arg.c_str()));
        }

        return argv;
    }
};
