#ifndef ANGELSCRIPTDEBUGGER_BASEPROTOCOL_HPP
#define ANGELSCRIPTDEBUGGER_BASEPROTOCOL_HPP
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "../../extern/json.hpp"
#include "Utils.hpp"

namespace DebugAdapterProtocol {
    static size_t current_sequence = 0;
    struct ProtocolMessage {
        explicit ProtocolMessage(std::string t) : seq(current_sequence++), type(std::move(t)) {}
        ProtocolMessage(std::string t, size_t s) : seq(s), type(std::move(t)) {}

        size_t seq;
        std::string type{"event"};

        static ProtocolMessage* FromJson(nlohmann::json& j);
        [[nodiscard]] virtual nlohmann::json ToJson() const { return {{"seq", seq}, {"type", type}}; }

        virtual ~ProtocolMessage() = default;
    };

    struct RequestArguments {
        RequestArguments(){};
        explicit RequestArguments(nlohmann::json&) {}
        virtual ~RequestArguments() = default;
        virtual nlohmann::json ToJson() const { return nlohmann::json ::object(); }
    };

    struct Request : public ProtocolMessage {
        Request() : ProtocolMessage("request") {}
        virtual std::string GetCommand() const = 0;
        virtual std::optional<RequestArguments*> GetArguments() const = 0;

        static ProtocolMessage* FromJson(nlohmann::json& j);
        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ProtocolMessage::ToJson();
            o["command"] = GetCommand();
            if (GetArguments().has_value()) {
                o["body"] = GetArguments().value()->ToJson();
            }
            return o;
        }
    };

    template <class T>
    concept IsRequestArguments = std::is_base_of<RequestArguments, T>::value;

    template <IsRequestArguments T, const char* S> struct DefinedRequest : public Request {
        std::optional<T*> arguments;

        static ProtocolMessage* FromJson(nlohmann::json& j);
        ~DefinedRequest() override {
            if (arguments.has_value()) {
                delete arguments.value();
            }
        }
        [[nodiscard]] std::string GetCommand() const override { return S; }
        [[nodiscard]] std::optional<RequestArguments*> GetArguments() const override { return arguments; }
    };

    struct EventBody {
        EventBody() = default;
        explicit EventBody(nlohmann::json&) {}
        virtual ~EventBody() = default;

        [[nodiscard]] virtual nlohmann::json ToJson() const { return nlohmann::json::object(); };
    };

    template <class T>
    concept IsEventBody = std::is_base_of<EventBody, T>::value;

    template <IsEventBody T, const char* S> struct Event : public ProtocolMessage {
        Event() : ProtocolMessage("event") {}
        explicit Event(T* b) : ProtocolMessage("event"), body(b) {}

        std::optional<T*> body;

        ~Event() override {
            if (body.has_value()) {
                delete body.value();
            }
        }

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ProtocolMessage::ToJson();
            o["event"] = S;
            if (body.has_value()) {
                o["body"] = body.value()->ToJson();
            }
            return o;
        }
    };

    struct ResponseBody {
        ResponseBody() = default;
        explicit ResponseBody(nlohmann::json&) {}
        virtual ~ResponseBody() = default;

        [[nodiscard]] virtual nlohmann::json ToJson() const { return nlohmann::json::object(); };
    };

    template <class T>
    concept IsResponseBody = std::is_base_of<ResponseBody, T>::value;

    struct Response : public ProtocolMessage {
        Response(size_t seq) : ProtocolMessage("response", seq), request_seq(seq) {}
        size_t request_seq;
        bool success = true;
        std::optional<std::string> message;

        virtual std::string GetCommand() const = 0;
        virtual std::optional<ResponseBody*> GetBody() const = 0;
    };

    template <IsResponseBody T, const char* S> struct DefinedResponse : public Response {
        DefinedResponse(size_t seq) : Response(seq) {}

        std::optional<T*> body;

        [[nodiscard]] std::string GetCommand() const override { return S; }
        [[nodiscard]] std::optional<ResponseBody*> GetBody() const override { return body; }

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ProtocolMessage::ToJson();
            o["request_seq"] = request_seq;
            o["success"] = success;
            o["command"] = GetCommand();
            JsonSerializeOptional(o, message);
            if (body.has_value()) {
                o["body"] = body.value()->ToJson();
            }
            return o;
        }
    };

}
#endif // ANGELSCRIPTDEBUGGER_BASEPROTOCOL_HPP
