#pragma once
#include "fmt/core.h"
#include "ll/api/utils/SystemUtils.h"
#include "mc/nbt/CompoundTag.h"
#include "mc/world/Container.h"
#include "mc/world/actor/player/Player.h"
#include "mc/world/item/ItemStack.h"
#include "mc/deps/core/math/Vec3.h"
#include "mc/world/level/BlockPos.h"
#include "mc/world/level/block/Block.h"
#include "mc/world/level/block/actor/BlockActor.h"

#define TEST_NEW_VALUE_TYPE

///////////////////////////////////////////////////////
// Remote Call API
// Mainly designed for scripting engines
// Please call it in MC_SERVER thread or in ScheduleAPI
// make sure the callback parameter type can be converted to json
//
// [Usage]
// RemoteCall::exportAs("TestNameSpace", "strSize", [](std::string const& arg) -> int { return arg.size(); });
//
// // in other plugin
// auto strSize = RemoteCall::importAs<int(std::string const& arg)>("TestNameSpace", "strSize");
// logger.info("Size of str: {}", strSize("12345678"));
//
// // in js plugin
// const strSize = ll.import("TestNameSpace", "strSize");
// logger.info(`Size of str: ${strSize("12345678")}`);
//
/////////////////////////////////////////////////////
namespace RemoteCall {
#ifdef TEST_NEW_VALUE_TYPE
// .....
struct NbtType {
    CompoundTag const* ptr = nullptr;
    bool               own = false;
    NbtType(std::unique_ptr<CompoundTag> tag) : ptr(tag.release()), own(true){};
    NbtType(CompoundTag const* ptr) : ptr(ptr), own(false){};
    inline std::unique_ptr<CompoundTag> tryGetUniquePtr() {
        if (!own) return {};
        own       = false;
        auto uptr = std::unique_ptr<CompoundTag>(const_cast<CompoundTag*>(ptr));
        ptr       = nullptr;
        return std::move(uptr);
    }
    template <typename RTN>
    inline RTN get() = delete;
    template <>
    inline CompoundTag const* get() {
        return ptr;
    };
    template <>
    inline CompoundTag* get() {
        return const_cast<CompoundTag*>(ptr);
    };
    template <>
    inline std::unique_ptr<CompoundTag> get() {
        return tryGetUniquePtr();
    };
};

struct ItemType {
    ItemStack const* ptr = nullptr;
    bool             own = false;
    ItemType(std::unique_ptr<ItemStack> tag) : ptr(tag.release()), own(true){};
    ItemType(ItemStack const* ptr) : ptr(ptr), own(false){};
    inline std::unique_ptr<ItemStack> tryGetUniquePtr() {
        if (!own) return {};
        own       = false;
        auto uptr = std::unique_ptr<ItemStack>(const_cast<ItemStack*>(ptr));
        ptr       = nullptr;
        return std::move(uptr);
    }
    template <typename RTN>
    inline RTN get() = delete;
    template <>
    inline ItemStack const* get() {
        return ptr;
    };
    template <>
    inline ItemStack* get() {
        return const_cast<ItemStack*>(ptr);
    };
    template <>
    inline std::unique_ptr<ItemStack> get() {
        return tryGetUniquePtr();
    };
};

struct BlockType {
    Block const* block;
    BlockPos     blockPos;
    int          dimension;
    BlockType(Block* block) : block(block){};
    BlockType(Block const* ptr) : block(ptr) {
        blockPos  = BlockPos::ZERO();
        dimension = 0;
    };
    template <typename RTN>
    inline RTN get() = delete;
    template <>
    inline Block const* get() {
        return block;
    };
};

struct NumberType {
    __int64 i = 0;
    double  f = 0;
    NumberType(__int64 i, double f) : i(i), f(f){};
    template <typename T>
    std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, NumberType&> operator=(T v) {
        i = static_cast<__int64>(v);
        f = static_cast<double>(v);
    }
    NumberType(double v) : i(static_cast<__int64>(v)), f(static_cast<double>(v)){};
    NumberType(float v) : i(static_cast<__int64>(v)), f(static_cast<double>(v)){};
    NumberType(__int64 v) : i(static_cast<__int64>(v)), f(static_cast<double>(v)){};
    NumberType(int v) : i(static_cast<__int64>(v)), f(static_cast<double>(v)){};
    NumberType(short v) : i(static_cast<__int64>(v)), f(static_cast<double>(v)){};
    NumberType(char v) : i(static_cast<__int64>(v)), f(static_cast<double>(v)){};
    NumberType(unsigned __int64 v) : i(static_cast<__int64>(v)), f(static_cast<double>(v)){};
    NumberType(unsigned int v) : i(static_cast<__int64>(v)), f(static_cast<double>(v)){};
    NumberType(unsigned short v) : i(static_cast<__int64>(v)), f(static_cast<double>(v)){};
    NumberType(unsigned char v) : i(static_cast<__int64>(v)), f(static_cast<double>(v)){};
    template <typename RTN>
    inline std::enable_if_t<std::is_integral_v<RTN>, RTN> get() {
        return static_cast<RTN>(i);
    };
    template <typename RTN>
    inline std::enable_if_t<std::is_floating_point_v<RTN>, RTN> get() {
        return static_cast<RTN>(f);
    };
};

struct WorldPosType {
    Vec3 pos   = Vec3::ZERO();
    int  dimId = 3; // VanillaDimensions::Undefined;
    WorldPosType(Vec3 const& pos, int dimId = 3) : pos(pos), dimId(dimId){};
    WorldPosType(std::pair<Vec3, int> const& pos) : pos(pos.first), dimId(pos.second){};
    template <typename RTN>
    inline RTN get() = delete;
    template <>
    inline Vec3 get() {
        return pos;
    };
    template <>
    inline BlockPos get() {
        return BlockPos(pos);
    };
    template <>
    inline std::pair<Vec3, int> get() {
        return std::make_pair(pos, dimId);
    };
    template <>
    inline std::pair<BlockPos, int> get() {
        return std::make_pair(BlockPos(pos), dimId);
    };
};

struct BlockPosType {
    BlockPos pos   = BlockPos::ZERO();
    int      dimId = 0;
    BlockPosType(BlockPos const& pos, int dimId = 0) : pos(pos), dimId(dimId){};
    BlockPosType(std::pair<BlockPos, int> const& pos) : pos(pos.first), dimId(pos.second){};
    template <typename RTN>
    inline RTN get() = delete;
    template <>
    inline BlockPos get() {
        return pos;
    };
    template <>
    inline std::pair<BlockPos, int> get() {
        return std::make_pair(pos, dimId);
    };
    template <>
    inline Vec3 get() {
        return pos;
    };
    template <>
    inline std::pair<Vec3, int> get() {
        return std::make_pair(pos, dimId);
    };
};

// std::string  -> json
// std::string* -> bytes
#define ExtraType                                                                                                      \
    std::nullptr_t, NumberType, Player*, Actor*, BlockActor*, Container*, WorldPosType, BlockPosType, ItemType,        \
        BlockType, NbtType
#define ElementType bool, std::string, ExtraType
template <typename _Ty, class... _Types>
static constexpr bool is_one_of_v =
    std::_Meta_find_unique_index<std::variant<_Types...>, _Ty>::value < sizeof...(_Types);
template <typename _Ty>
static constexpr bool is_extra_type_v = std::_Is_any_of_v<_Ty, ExtraType>;

static_assert(sizeof(std::variant<ElementType>) == sizeof(std::string) + 8);

template <typename>
constexpr bool is_vector_v = false;
template <class _Ty, class _Alloc>
constexpr bool is_vector_v<std::vector<_Ty, _Alloc>> = true;
template <typename>
constexpr bool is_map_v = false;
template <class _Kty, class _Ty, class _Pr, class _Alloc>
constexpr bool is_map_v<std::map<_Kty, _Ty, _Pr, _Alloc>> = true;
template <class _Kty, class _Ty, class _Hasher, class _Keyeq, class _Alloc>
constexpr bool is_map_v<std::unordered_map<_Kty, _Ty, _Hasher, _Keyeq, _Alloc>> = true;
using Value                                                                     = std::variant<ElementType>;
// struct Value
//{
//     std::variant<ElementType> value;
//     Value(bool v)
//         : value(v){};
//     Value(__int64 v)
//         : value(v){};
//     Value(double v)
//         : value(v){};
//     Value(std::string const& v)
//         : value(v){};
//     Value(std::string* v)
//         : value(v){};
//     Value(Player* v)
//         : value(v){};
//     Value(Actor* v)
//         : value(v){};
//     Value(ItemStack* v)
//         : value(v){};
//     Value(Block* v)
//         : value(v){};
//     Value(BlockActor* v)
//         : value(v){};
//     Value(Container* v)
//         : value(v){};
//     Value(Vec3* v)
//         : value(v){};
//     Value(BlockPos* v)
//         : value(v){};
//     Value(CompoundTag* v)
//         : value(v){};
//     operator std::variant<ElementType>()
//     {
//         return value;
//     }
// };
struct ValueType {
    using ArrayType  = std::vector<ValueType>;
    using ObjectType = std::unordered_map<std::string, ValueType>;
    using Type       = std::variant<Value, ArrayType, ObjectType>;
    Type value;
    ValueType() : value({}){};
    // ValueType(ValueType const& v) = delete;
    // ValueType(Value const& v) = delete;
    ValueType(Value&& v) : value(std::move(v)){};
    ValueType(Value v) : value(std::move(v)){};
    // ValueType(ValueType&& v) noexcept
    //     : value(std::move(v.value)){};
    ValueType(std::vector<ValueType>&& v) : value(std::move(v)){};
    ValueType(std::unordered_map<std::string, ValueType>&& v) : value(std::move(v)){};
    template <typename T>
    ValueType(T const& v) : value(Value(v)){};
};

template <typename _Ty>
static constexpr bool is_supported_type_v =
    std::is_void_v<_Ty> || is_one_of_v<_Ty, ElementType> || std::is_assignable_v<NumberType, _Ty>
    || std::is_assignable_v<NbtType, _Ty> || std::is_assignable_v<BlockType, _Ty> || std::is_assignable_v<ItemType, _Ty>
    || std::is_assignable_v<WorldPosType, _Ty> || std::is_assignable_v<BlockPosType, _Ty>
    || std::is_base_of_v<Player, std::remove_pointer_t<_Ty>> || std::is_base_of_v<Actor, std::remove_pointer_t<_Ty>>;

template <typename RTN>
RTN extract(ValueType&& val);
template <typename T>
ValueType pack(T val);

template <typename RTN>
RTN extractValue(Value&& value) {
    using Type = std::remove_const_t<std::remove_reference_t<RTN>>;
    static_assert(is_supported_type_v<Type>, "Unsupported Type:");
    if constexpr (is_one_of_v<Type, ElementType>) return std::get<Type>(value);
    else if constexpr (std::is_assignable_v<NumberType, RTN>) return std::get<NumberType>(value).get<Type>();
    else if constexpr (std::is_assignable_v<NbtType, RTN>) return std::get<NbtType>(value).get<Type>();
    else if constexpr (std::is_assignable_v<ItemType, RTN>) return std::get<ItemType>(value).get<Type>();
    else if constexpr (std::is_assignable_v<BlockType, RTN>) return std::get<BlockType>(value).get<Type>();
    else if constexpr (std::is_assignable_v<WorldPosType, RTN>) return std::get<WorldPosType>(value).get<Type>();
    else if constexpr (std::is_assignable_v<BlockPosType, RTN>) return std::get<BlockPosType>(value).get<Type>();
    else if constexpr (std::is_base_of_v<Player, std::remove_pointer_t<RTN>>)
        return static_cast<RTN>(std::get<Player*>(value));
    else if constexpr (std::is_base_of_v<Actor, std::remove_pointer_t<RTN>>)
        return static_cast<RTN>(std::get<Actor*>(value));
    else if constexpr (std::is_void_v<Type>) return;
    else throw std::exception(fmt::format(__FUNCTION__ " - Unsupported Type: {}", typeid(RTN).name()).c_str());
}

template <typename RTN>
bool extractValue(std::vector<ValueType>& value, std::vector<RTN>& rtn) {
    for (ValueType& val : value) {
        rtn.emplace_back(std::move(extract<RTN>(std::move(val))));
    }
    return true;
}

template <typename RTN>
bool extractValue(std::unordered_map<std::string, ValueType>& value, std::unordered_map<std::string, RTN>& rtn) {
    for (auto& [key, val] : value) {
        rtn.emplace(key, std::move(extract<RTN>(std::move(val))));
    }
    return true;
}

template <typename RTN>
RTN extract(ValueType&& val) {
    if constexpr (is_vector_v<RTN>) {
        RTN rtn{};
        extractValue(std::get<std::vector<ValueType>>(val.value), rtn);
        return std::move(rtn);
    } else if constexpr (is_map_v<RTN>) {
        RTN rtn{};
        extractValue(std::get<std::unordered_map<std::string, ValueType>>(val.value), rtn);
        return std::move(rtn);
    } else return extractValue<RTN>(std::move(std::get<Value>(val.value)));
}

template <typename T>
ValueType packValue(T val) {
    using RawType = std::remove_reference_t<std::remove_const_t<T>>;
    static_assert(is_supported_type_v<RawType>, "Unsupported Type");
    if constexpr (is_one_of_v<RawType, ElementType>) return ValueType(std::forward<T>(val));
    else if constexpr (std::is_assignable_v<NumberType, T>) return ValueType(NumberType{std::forward<T>(val)});
    else if constexpr (std::is_assignable_v<NbtType, T>) return ValueType(NbtType(std::forward<T>(val)));
    else if constexpr (std::is_assignable_v<ItemType, T>) return ValueType(ItemType(std::forward<T>(val)));
    else if constexpr (std::is_assignable_v<BlockType, T>) return ValueType(BlockType(std::forward<T>(val)));
    else if constexpr (std::is_assignable_v<WorldPosType, T>) return ValueType(WorldPosType(std::forward<T>(val)));
    else if constexpr (std::is_assignable_v<BlockPosType, T>) return ValueType(BlockPosType(std::forward<T>(val)));
    else if constexpr (std::is_base_of_v<Player, std::remove_pointer_t<T>>)
        return ValueType(static_cast<Player*>(std::forward<T>(val)));
    else if constexpr (std::is_base_of_v<Actor, std::remove_pointer_t<T>>)
        return ValueType(static_cast<Actor*>(std::forward<T>(val)));
    else if constexpr (std::is_void_v<RawType>) return {};
    throw std::runtime_error(fmt::format(__FUNCTION__ " - Unsupported Type: {}", typeid(T).name()).c_str());
}
template <typename T>
std::vector<ValueType> packArray(std::vector<T> const& val) {
    std::vector<ValueType> result;
    for (auto& v : val) {
        result.emplace_back(pack(v));
    }
    return result;
}
template <typename T>
std::unordered_map<std::string, ValueType> packObject(std::unordered_map<std::string, T> const& val) {
    std::unordered_map<std::string, ValueType> result;
    for (auto& [k, v] : val) {
        result.emplace(k, pack(v));
    }
    return result;
}

template <typename T>
ValueType pack(T val) {
    using RawType = std::remove_reference_t<std::remove_const_t<T>>;
    if constexpr (is_vector_v<RawType>) {
        return packArray(std::forward<T>(val));
    } else if constexpr (is_map_v<RawType>) {
        return packObject(std::forward<T>(val));
    } else return packValue(std::forward<T>(val));
}

#else

// Use string as value type because it is easy to convert between script types and native types
using ValueType = std::string; // Json string

template <typename T>
std::remove_reference_t<T> extract(ValueType const& val) {
    return nlohmann::json::parse(val).get<std::remove_reference_t<T>>();
}

template <typename T>
ValueType pack(T const& val) {
    return nlohmann::json(val).dump();
}

#endif // TEST_NEW_VALUE_TYPE

using CallbackFn = std::function<ValueType(std::vector<ValueType>)>;

struct ExportedFuncData {
    void*      handle;
    CallbackFn callback;
};

__declspec(dllexport) extern CallbackFn const EMPTY_FUNC;
__declspec(dllexport) bool exportFunc(
    std::string const& nameSpace,
    std::string const& funcName,
    CallbackFn&&       callback,
    void*              handle = ll::sys_utils::getCurrentModuleHandle()
);
__declspec(dllexport) CallbackFn const& importFunc(std::string const& nameSpace, std::string const& funcName);

inline ValueType _expandArg(std::vector<ValueType>& args, int& index) { return std::move(args[--index]); }

template <typename RTN, typename... Args>
inline bool
_exportAs(std::string const& nameSpace, std::string const& funcName, std::function<RTN(Args...)>&& callback) {
    CallbackFn cb = [callback = std::move(callback)](std::vector<ValueType> args) -> ValueType {
        if (sizeof...(Args) != args.size()) return std::move(ValueType());
        int index = sizeof...(Args);
        if constexpr (std::is_void_v<RTN>) {
            callback(extract<Args>(_expandArg(args, index))...);
            return std::move(ValueType());
        } else {
            return pack(callback(extract<Args>(_expandArg(args, index))...));
        }
    };
    return exportFunc(nameSpace, funcName, std::move(cb), ll::sys_utils::getCurrentModuleHandle());
}

__declspec(dllexport) bool hasFunc(std::string const& nameSpace, std::string const& funcName);
__declspec(dllexport) bool removeFunc(std::string const& nameSpace, std::string const& funcName);
__declspec(dllexport) int removeNameSpace(std::string const& nameSpace);
__declspec(dllexport) int removeFuncs(std::vector<std::pair<std::string, std::string>>& funcs);
__declspec(dllexport) void _onCallError(std::string const& msg, void* handle = ll::sys_utils::getCurrentModuleHandle());

template <typename RTN, typename... Args>
inline bool _importAs(std::string const& nameSpace, std::string const& funcName, std::function<RTN(Args...)>& func) {
    func = [nameSpace, funcName](Args... args) -> RTN {
        auto& rawFunc = importFunc(nameSpace, funcName);
        if (!rawFunc) {
            _onCallError(fmt::format("Fail to import! Function [{}::{}] has not been exported", nameSpace, funcName));
            return RTN();
        }
        std::vector<ValueType> params = {pack(std::forward<Args>(args))...};
        ValueType&&            res    = rawFunc(std::move(params));
        return extract<RTN>(std::move(res));
    };
    return true;
}

template <typename CB, typename Func = std::conditional_t<std::is_function_v<CB>, std::function<CB>, CB>>
inline Func importAs(std::string const& nameSpace, std::string const& funcName) {
    Func callback{};
    _importAs(nameSpace, funcName, callback);
    return std::move(callback);
}

template <typename CB>
inline bool exportAs(std::string const& nameSpace, std::string const& funcName, CB&& callback) {
    return _exportAs(nameSpace, funcName, std::function(std::move(callback)));
}

} // namespace RemoteCall
