

// begin --- DebugAdapterProtocol --- 



// end --- DebugAdapterProtocol --- 



// begin --- AngelscriptDebugger.cpp --- 



// begin --- AngelscriptDebugger.hpp --- 

#ifndef ANGELSCRIPTDEBUGGER_ANGELSCRIPTDEBUGGER_HPP
#define ANGELSCRIPTDEBUGGER_ANGELSCRIPTDEBUGGER_HPP
#include <angelscript.h>
#include <iostream>
#include <unordered_set>
#include <variant>

// begin --- Breakpoint.hpp --- 

#ifndef ANGELSCRIPTDEBUGGER_BREAKPOINT_HPP
#define ANGELSCRIPTDEBUGGER_BREAKPOINT_HPP
#include <string>
#include <limits>

struct Breakpoint {
    std::string Section;
    int32_t Line;

    bool operator==(const Breakpoint& other) const { return (Section == other.Section && Line == other.Line); }
};

namespace std {

    template <> struct hash<Breakpoint> {
        std::size_t operator()(const Breakpoint& k) const {
            using std::hash;
            using std::size_t;
            using std::string;

            return ((hash<string>()(k.Section) ^ (hash<int32_t>()(k.Line) << 1)) >> 1);
        }
    };
}

#endif // ANGELSCRIPTDEBUGGER_BREAKPOINT_HPP


// end --- Breakpoint.hpp --- 



// begin --- BaseProtocol.hpp --- 

#ifndef ANGELSCRIPTDEBUGGER_BASEPROTOCOL_HPP
#define ANGELSCRIPTDEBUGGER_BASEPROTOCOL_HPP
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

// begin --- Utils.hpp --- 

#ifndef ANGELSCRIPTDEBUGGER_UTILS_HPP
#define ANGELSCRIPTDEBUGGER_UTILS_HPP

#define JsonSerializeOptional(obj, field)                                                                              \
    if (field.has_value())                                                                                             \
    obj[#field] = field.value()

#define JsonDeserializeOptional(obj, field)                                                                            \
    if (!obj[#field].empty())                                                                                          \
        field = obj[#field]

#endif // ANGELSCRIPTDEBUGGER_UTILS_HPP


// end --- 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


// end --- BaseProtocol.hpp --- 



class AngelscriptDebugger {
public:
    ~AngelscriptDebugger() {
        _server->close();
        delete _server;
    }

    void Run(uint16_t port);
    void Stop() { _server->close(); }

    void RegisterContext(asIScriptContext* ctx);
    void Continue() {
        std::cout << "Continuing from suspension" << std::endl;
        _storedVariableReferences.clear();
        while (!_pausedContexts.empty()) {
            auto* ctx = _pausedContexts[_pausedContexts.size() - 1];
            _pausedContexts.pop_back();
            std::thread([](asIScriptContext* ctx) { ctx->Execute(); }, ctx).detach();
        }
    }
    bool HasDebuggerAttached() const {
        for (const auto& c : _connections) {
            if (c->is_open()) {
                return true;
            }
        }
        return false;
    }

    size_t StoreVariable(void* address, int typeId, const std::optional<std::function<void(void* address)>>& release) {
        _storedVariableReferences.emplace_back(std::make_unique<PointerVariable>(address, typeId, release));
        return _storedVariableReferences.size();
    }

private:
    static void on_line_callback(asIScriptContext* ctx, AngelscriptDebugger* dbg);
    static void on_exception_callback(asIScriptContext* ctx, AngelscriptDebugger* d);

    void EnterBreakpoint(asIScriptContext* ctx, const std::string& section, size_t line);

    [[noreturn]] void AcceptLoop();
    void ClientLoop(asio::ip::tcp::socket&);
    void Send(DebugAdapterProtocol::ProtocolMessage* msg);
    void Send(asio::ip::tcp::socket* client, DebugAdapterProtocol::ProtocolMessage* msg);
    void OnMessage(asio::ip::tcp::socket* client, DebugAdapterProtocol::ProtocolMessage* msg);
    void OnRequest(asio::ip::tcp::socket* client, DebugAdapterProtocol::Request* msg);
    std::string GetResolvedScriptPath(const char* scriptSection);

    asio::io_context _ioContext;
    asio::ip::tcp::acceptor* _server;
    std::unordered_set<asio::ip::tcp::socket*> _connections;

    std::unordered_map<std::string, std::unordered_set<size_t>> _breakpoints;
    std::vector<asIScriptContext*> _pausedContexts;
    std::optional<std::string> _scriptPath;

    struct StackScope {
        size_t StackLevel;
        uint8_t Type;

        StackScope(size_t stackLevel, uint8_t type) : StackLevel(stackLevel), Type(type) {}
    };
    struct PointerVariable {
        void* Address;
        int TypeID;
        std::optional<std::function<void(void*)>> Release;

        PointerVariable(void* address, int typeId, std::optional<std::function<void(void*)>> release)
            : Address(address), TypeID(typeId), Release(release) {}
        ~PointerVariable() {
            if (Release.has_value()) {
                Release.value()(Address);
            }
        }
    };

    std::vector<std::variant<std::unique_ptr<StackScope>, std::unique_ptr<PointerVariable>>> _storedVariableReferences;
};

#endif // ANGELSCRIPTDEBUGGER_ANGELSCRIPTDEBUGGER_HPP


// end --- AngelscriptDebugger.hpp --- 



// begin --- ASVariableFormatter.hpp --- 

#ifndef ANGELSCRIPTDEBUGGER_ASVARIABLEFORMATTER_HPP
#define ANGELSCRIPTDEBUGGER_ASVARIABLEFORMATTER_HPP
#include <angelscript.h>

// begin --- Types.hpp --- 

#ifndef ANGELSCRIPTDEBUGGER_TYPES_HPP
#define ANGELSCRIPTDEBUGGER_TYPES_HPP
#include <optional>
#include <string>

namespace DebugAdapterProtocol {
    struct Source {
        std::optional<std::string> name;
        std::optional<std::string> path;
        std::optional<size_t> sourceReference;

        Source() {}
        Source(std::string path) : path(path) {}
        explicit Source(nlohmann::json& j) {
            JsonDeserializeOptional(j, name);
            JsonDeserializeOptional(j, path);
            JsonDeserializeOptional(j, sourceReference);
        }

        [[nodiscard]] nlohmann::json ToJson() const {
            nlohmann::json o = nlohmann::json::object();
            JsonSerializeOptional(o, name);
            JsonSerializeOptional(o, path);
            JsonSerializeOptional(o, sourceReference);
            return o;
        }
    };

    struct Breakpoint {
        std::optional<size_t> id;
        bool verified;
        std::optional<std::string> message;
        std::optional<Source> source;
        std::optional<size_t> line;
        std::optional<size_t> column;
        std::optional<size_t> endLine;
        std::optional<size_t> endColumn;
        std::optional<std::string> instructionReference;
        std::optional<size_t> offset;

        Breakpoint(size_t id, Source& s, size_t line) : id(id), verified(true), source(s), line(line) {}
        [[nodiscard]] nlohmann::json ToJson() const {
            nlohmann::json o;
            JsonSerializeOptional(o, id);
            o["verified"] = verified;
            JsonSerializeOptional(o, message);
            if (source.has_value()) {
                o["source"] = source.value().ToJson();
            }
            JsonSerializeOptional(o, line);
            JsonSerializeOptional(o, column);
            JsonSerializeOptional(o, endLine);
            JsonSerializeOptional(o, endColumn);
            JsonSerializeOptional(o, instructionReference);
            JsonSerializeOptional(o, offset);
            return o;
        }
    };

    struct BreakpointLocation {
        size_t line;
        std::optional<size_t> column;
        std::optional<size_t> endLine;
        std::optional<size_t> endColumn;
    };

    struct SourceBreakpoint {
        size_t line;
        std::optional<size_t> column;
        std::optional<std::string> condition;
        std::optional<std::string> hitCondition;
        std::optional<std::string> logMessage;

        SourceBreakpoint(nlohmann::json j) {
            line = j["line"];
            JsonDeserializeOptional(j, column);
            JsonDeserializeOptional(j, condition);
            JsonDeserializeOptional(j, hitCondition);
            JsonDeserializeOptional(j, logMessage);
        }

        [[nodiscard]] nlohmann::json ToJson() const {
            nlohmann::json o;
            o["line"] = line;
            JsonSerializeOptional(o, column);
            JsonSerializeOptional(o, condition);
            JsonSerializeOptional(o, hitCondition);
            JsonSerializeOptional(o, logMessage);
            return o;
        }
    };

    struct Module {
        std::string id;
        std::string name;
        std::optional<std::string> path;
        std::optional<bool> isOptimized;
        std::optional<bool> isUserCode;
        std::optional<std::string> version;
        std::optional<std::string> symbolStatus;
        std::optional<std::string> symbolFilePath;
        std::optional<std::string> dateTimeStamp;
        std::optional<std::string> addressRange;

        [[nodiscard]] nlohmann::json ToJson() const {
            nlohmann::json o;
            o["id"] = id;
            o["name"] = name;
            JsonSerializeOptional(o, path);
            JsonSerializeOptional(o, isOptimized);
            JsonSerializeOptional(o, isUserCode);
            JsonSerializeOptional(o, version);
            JsonSerializeOptional(o, symbolStatus);
            JsonSerializeOptional(o, symbolFilePath);
            JsonSerializeOptional(o, dateTimeStamp);
            JsonSerializeOptional(o, addressRange);
            return o;
        }
    };

    struct Thread {
        size_t id;
        std::string name;
        Thread(size_t id, std::string name) : id(id), name(name) {}

        [[nodiscard]] nlohmann::json ToJson() const {
            nlohmann::json o;
            o["id"] = id;
            o["name"] = name;
            return o;
        }
    };

    struct StackFrame {
        size_t id;
        std::string name;
        std::optional<Source> source;
        size_t line;
        size_t column;
        std::optional<size_t> endLine;
        std::optional<size_t> endColumn;
        std::optional<bool> canRestart = false;
        std::optional<std::string> instructionPointerReference;

        StackFrame(){};
        StackFrame(size_t id, std::string name, Source source, size_t line, size_t column)
            : id(id), name(name), source(source), line(line), column(column) {}

        [[nodiscard]] nlohmann::json ToJson() const {
            nlohmann::json o;
            o["id"] = id;
            o["name"] = name;
            if (source.has_value()) {
                o["source"] = source.value().ToJson();
            }
            o["line"] = line;
            o["column"] = column;
            JsonSerializeOptional(o, endLine);
            JsonSerializeOptional(o, endColumn);
            JsonSerializeOptional(o, canRestart);
            JsonSerializeOptional(o, instructionPointerReference);
            return o;
        }
    };

    struct Scope {
        std::string name;
        std::optional<std::string> presentationHint;
        size_t variablesReference;
        size_t namedVariables;
        size_t indexedVariables = 0;
        bool expensive = false;
        std::optional<Source> source;
        std::optional<size_t> line;
        std::optional<size_t> column;
        std::optional<size_t> endLine;
        std::optional<size_t> endColumn;

        Scope(std::string name, size_t reference, std::string presentationHint, size_t namedVariables)
            : name(std::move(name)), presentationHint(std::move(presentationHint)), variablesReference(reference),
              namedVariables(namedVariables) {}

        [[nodiscard]] nlohmann::json ToJson() const {
            nlohmann::json o;
            o["name"] = name;
            JsonSerializeOptional(o, presentationHint);
            o["variablesReference"] = variablesReference;
            o["namedVariables"] = namedVariables;
            o["indexedVariables"] = indexedVariables;
            o["expensive"] = expensive;
            if (source.has_value()) {
                o["source"] = source.value().ToJson();
            }
            JsonSerializeOptional(o, line);
            JsonSerializeOptional(o, column);
            JsonSerializeOptional(o, endLine);
            JsonSerializeOptional(o, endColumn);
            return o;
        }
    };

    struct VariablePresentationHint {
        std::optional<std::string> kind;
        std::optional<std::vector<std::string>> attributes;
        std::optional<std::string> visibility;

        [[nodiscard]] nlohmann::json ToJson() const {
            nlohmann::json o;
            JsonSerializeOptional(o, kind);
            JsonSerializeOptional(o, attributes);
            JsonSerializeOptional(o, visibility);
            return o;
        }
    };

    struct Variable {
        std::string name;
        std::string value;
        std::optional<std::string> type;
        std::optional<VariablePresentationHint> presentationHint;
        std::optional<std::string> evaluateName;
        std::optional<size_t> variablesReference = 0;
        std::optional<size_t> namedVariables = 0;
        std::optional<size_t> indexedVariables = 0;
        std::optional<std::string> memoryReference;

        [[nodiscard]] nlohmann::json ToJson() const {
            nlohmann::json o;
            o["name"] = name;
            o["value"] = value;
            JsonSerializeOptional(o, type);
            if (presentationHint.has_value()) {
                o["presentationHint"] = presentationHint.value().ToJson();
            }
            JsonSerializeOptional(o, evaluateName);
            JsonSerializeOptional(o, variablesReference);
            JsonSerializeOptional(o, namedVariables);
            JsonSerializeOptional(o, indexedVariables);
            JsonSerializeOptional(o, memoryReference);

            return o;
        }

        Variable(std::string name, std::string value, std::string type,
                 std::optional<VariablePresentationHint> presentationHint = {})
            : name(std::move(name)), value(std::move(value)), type(std::move(type)),
              presentationHint(presentationHint) {}

        static Variable FromString(std::string name, std::string value) {
            return Variable(
                name, value, "string",
                VariablePresentationHint{.kind = "data", .attributes = std::vector<std::string>{"rawString"}});
        }

        static Variable FromNull(std::string name, std::string type) {
            return Variable(name, "null", type,
                            VariablePresentationHint{.kind = "data", .attributes = std::vector<std::string>{}});
        }

        static Variable FromPointer(std::string name, std::string type, std::string display, size_t variableReference) {
            auto v = Variable(
                std::move(name), std::move(display), type,
                VariablePresentationHint{.kind = "class", .attributes = std::vector<std::string>{"hasObjectId"}});
            v.variablesReference = variableReference;
            return v;
        }
    };
}

#endif // ANGELSCRIPTDEBUGGER_TYPES_HPP


// end --- Types.hpp --- 



class ASVariableFormatter {
public:
    static nlohmann::json GetAsJsonValue(asIScriptEngine* engine, int, void*, int32_t depth = 0);
    static DebugAdapterProtocol::Variable GetAsDAPVariable(asIScriptEngine* engine, asIScriptContext* ctx,
                                                           AngelscriptDebugger* debugger, const std::string& name, int,
                                                           void*);
    static void GetChildDAPVariables(std::vector<DebugAdapterProtocol::Variable>&, asIScriptEngine* engine,
                                     asIScriptContext* ctx, AngelscriptDebugger* debugger, int, void*);
};

#endif // ANGELSCRIPTDEBUGGER_ASVARIABLEFORMATTER_HPP


// end --- ASVariableFormatter.hpp --- 



// begin --- Events.hpp --- 

#ifndef ANGELSCRIPTDEBUGGER_EVENTS_HPP
#define ANGELSCRIPTDEBUGGER_EVENTS_HPP

namespace DebugAdapterProtocol {
#define EventDefinition(name, jsonName, ...)                                                                           \
    static const char __c##name[] = jsonName;                                                                          \
    struct name##EventBody : public EventBody __VA_ARGS__;                                                             \
    using name##Event = Event<name##EventBody, __c##name>

    EventDefinition(Breakpoint, "breakpoint", {
        std::string reason;
        Breakpoint breakpoint;
        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            o["reason"] = reason;
            o["breakpoint"] = breakpoint.ToJson();
            return o;
        }
    });

    EventDefinition(Continued, "continued", {
        size_t threadId;
        std::optional<bool> allThreadsContinued;
        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            o["threadId"] = threadId;
            JsonSerializeOptional(o, allThreadsContinued);
            return o;
        }
    });

    EventDefinition(Exited, "exited", {
        int64_t exitCode;
        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            o["exitCode"] = exitCode;
            return o;
        }
    });

    EventDefinition(Initialized, "initialized", {});

    EventDefinition(Invalidated, "invalidated", {
        std::optional<std::vector<std::string>> invalidatedAreas;
        std::optional<size_t> threadId;
        std::optional<size_t> stackFrameId;

        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            JsonSerializeOptional(o, invalidatedAreas);
            JsonSerializeOptional(o, threadId);
            JsonSerializeOptional(o, stackFrameId);
            return o;
        }
    });

    EventDefinition(LoadedSource, "loadedSource", {
        std::string reason;
        Source source;
        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            o["reason"] = reason;
            o["source"] = source.ToJson();
            return o;
        }
    });

    EventDefinition(Memory, "memory", {
        std::string memoryReference;
        size_t offset;
        size_t count;
        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            o["memoryReference"] = memoryReference;
            o["offset"] = offset;
            o["count"] = count;
            return o;
        }
    });

    EventDefinition(Module, "module", {
        std::string reason;
        Module module;
        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            o["reason"] = reason;
            o["module"] = module.ToJson();
            return o;
        }
    });

    EventDefinition(Output, "output", {
        std::optional<std::string> category;
        std::string output;
        std::optional<std::string> group;
        std::optional<int32_t> variablesReference;
        std::optional<Source> source;
        std::optional<size_t> line;
        std::optional<size_t> column;
        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            JsonSerializeOptional(o, category);
            o["output"] = output;
            JsonSerializeOptional(o, group);
            JsonSerializeOptional(o, variablesReference);
            if (source.has_value()) {
                o["source"] = source.value().ToJson();
            }
            JsonSerializeOptional(o, line);
            JsonSerializeOptional(o, column);
            return o;
        }
    });

    EventDefinition(Process, "process", {
        std::string name;
        std::optional<size_t> systemProcessId;
        std::optional<bool> isLocalProcess;
        std::optional<std::string> startMethod;
        std::optional<size_t> pointerSize;

        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            o["name"] = name;
            JsonSerializeOptional(o, systemProcessId);
            JsonSerializeOptional(o, isLocalProcess);
            JsonSerializeOptional(o, startMethod);
            JsonSerializeOptional(o, pointerSize);
            return o;
        }
    });

    EventDefinition(ProgressEnd, "progressEnd", {
        std::string progressId;
        std::optional<std::string> message;

        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            o["progressId"] = progressId;
            JsonSerializeOptional(o, message);
            return o;
        }
    });

    EventDefinition(ProgressStart, "progressStart", {
        std::string progressId;
        std::string title;
        std::optional<size_t> requestId;
        std::optional<bool> cancellable;
        std::optional<std::string> message;
        std::optional<float> percentage;

        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            o["progressId"] = progressId;
            o["title"] = title;
            JsonSerializeOptional(o, requestId);
            JsonSerializeOptional(o, cancellable);
            JsonSerializeOptional(o, message);
            JsonSerializeOptional(o, percentage);
            return o;
        }
    });

    EventDefinition(ProgressUpdate, "progressUpdate", {
        std::string progressId;
        std::optional<std::string> message;
        std::optional<float> percentage;

        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            o["progressId"] = progressId;
            JsonSerializeOptional(o, message);
            JsonSerializeOptional(o, percentage);
            return o;
        }
    });

    EventDefinition(Stopped, "stopped", {
        std::string reason;
        std::optional<std::string> description;
        std::optional<size_t> number;
        std::optional<bool> preserveFocusHint = false;
        std::optional<std::string> text;
        std::optional<bool> allThreadsStopped = true;
        std::optional<std::vector<size_t>> hitBreakpointIds;

        explicit StoppedEventBody(size_t breakpoint) {
            reason = "breakpoint";
            hitBreakpointIds = {breakpoint};
        }

        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            o["reason"] = reason;
            JsonSerializeOptional(o, description);
            JsonSerializeOptional(o, number);
            JsonSerializeOptional(o, preserveFocusHint);
            JsonSerializeOptional(o, text);
            JsonSerializeOptional(o, allThreadsStopped);
            JsonSerializeOptional(o, hitBreakpointIds);
            return o;
        }
    });

    EventDefinition(terminated, "terminated", {});

    EventDefinition(Thread, "thread", {
        std::string reason;
        std::optional<size_t> threadId;

        nlohmann::json ToJson() const override {
            auto o = EventBody::ToJson();
            o["reason"] = reason;
            JsonSerializeOptional(o, threadId);
            return o;
        }
    });

}

#endif // ANGELSCRIPTDEBUGGER_EVENTS_HPP


// end --- Events.hpp --- 



// begin --- Requests.hpp --- 

#ifndef ANGELSCRIPTDEBUGGER_REQUESTS_HPP
#define ANGELSCRIPTDEBUGGER_REQUESTS_HPP
#include <memory>
#include <optional>
#include <string>
#include <vector>

namespace DebugAdapterProtocol {

#define RequestDefinition(name, jsonName, ...)                                                                         \
    static const char __c##name[] = jsonName;                                                                          \
    struct name##Arguments : public RequestArguments __VA_ARGS__;                                                      \
    using name##Request = DefinedRequest<name##Arguments, __c##name>

#define RequestResponseDefinition(name, jsonName, ...)                                                                 \
    static const char __cResponse##name[] = jsonName;                                                                  \
    struct name##ResponseBody : public ResponseBody __VA_ARGS__;                                                       \
    using name##Response = DefinedResponse<name##ResponseBody, __cResponse##name>

#define EmptyRequest(name, jsonName)                                                                                   \
    RequestDefinition(name, jsonName, {explicit name##Arguments(nlohmann::json & j) : RequestArguments(j){}})

    RequestDefinition(Attach, "attach", {
        size_t port;
        std::string scriptPath;

        explicit AttachArguments(nlohmann::json & j)
            : RequestArguments(j), port(j["port"]), scriptPath(j["scriptPath"]) {}
    });

    RequestDefinition(BreakpointLocations, "breakpointLocations", {
        Source source;
        size_t line;
        std::optional<size_t> column;
        std::optional<size_t> endLine;
        std::optional<size_t> endColumn;
        BreakpointLocationsArguments() {}
        explicit BreakpointLocationsArguments(nlohmann::json & j)
            : RequestArguments(j), source(j["source"]), line(j["line"]) {
            JsonDeserializeOptional(j, column);
            JsonDeserializeOptional(j, endLine);
            JsonDeserializeOptional(j, endColumn);
        }

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = RequestArguments::ToJson();
            o["source"] = source.ToJson();
            o["line"] = line;
            JsonSerializeOptional(o, column);
            JsonSerializeOptional(o, endLine);
            JsonSerializeOptional(o, endColumn);
            return o;
        }
    });

    EmptyRequest(Completions, "completions");
    EmptyRequest(ConfigurationDone, "configurationDone");
    EmptyRequest(Continue, "continue");
    EmptyRequest(DataBreakpointInfo, "dataBreakpointInfo");
    EmptyRequest(Disassemble, "disassemble");

    RequestDefinition(Disconnect, "disconnect", {
        std::optional<bool> restart;
        std::optional<bool> terminateDebuggee;
        std::optional<bool> suspendDebuggee;

        explicit DisconnectArguments(nlohmann::json & j) : RequestArguments(j) {
            JsonDeserializeOptional(j, restart);
            JsonDeserializeOptional(j, terminateDebuggee);
            JsonDeserializeOptional(j, suspendDebuggee);
        }
    });

    EmptyRequest(Evaluate, "evaluate");
    EmptyRequest(ExceptionInfo, "exceptionInfo");
    EmptyRequest(Goto, "goto");
    EmptyRequest(GotoTargets, "gotoTargets");
    EmptyRequest(Initialize, "initialize");
    RequestResponseDefinition(Initialize, "initialize", {
        std::optional<bool> supportsConfigurationDoneRequest = true;
        std::optional<bool> supportsFunctionBreakpoints;
        std::optional<bool> supportsConditionalBreakpoints;
        std::optional<bool> supportsHitConditionalBreakpoints;
        std::optional<bool> supportsEvaluateForHovers;
        std::optional<bool> supportsStepBack;
        std::optional<bool> supportsSetVariable;
        std::optional<bool> supportsRestartFrame;
        std::optional<bool> supportsGotoTargetsRequest;
        std::optional<bool> supportsStepInTargetsRequest;
        std::optional<bool> supportsCompletionsRequest;
        std::optional<bool> supportsModulesRequest;
        std::optional<bool> supportsRestartRequest;
        std::optional<bool> supportsExceptionOptions;
        std::optional<bool> supportsValueFormattingOptions;
        std::optional<bool> supportsExceptionInfoRequest;
        std::optional<bool> supportTerminateDebuggee;
        std::optional<bool> supportSuspendDebuggee;
        std::optional<bool> supportsDelayedStackTraceLoading;
        std::optional<bool> supportsLoadedSourcesRequest;
        std::optional<bool> supportsLogPoints;
        std::optional<bool> supportsTerminateThreadsRequest;
        std::optional<bool> supportsSetExpression;
        std::optional<bool> supportsTerminateRequest;
        std::optional<bool> supportsDataBreakpoints;
        std::optional<bool> supportsReadMemoryRequest;
        std::optional<bool> supportsWriteMemoryRequest;
        std::optional<bool> supportsDisassembleRequest;
        std::optional<bool> supportsCancelRequest;
        std::optional<bool> supportsBreakpointLocationsRequest;
        std::optional<bool> supportsClipboardContext;
        std::optional<bool> supportsSteppingGranularity;
        std::optional<bool> supportsInstructionBreakpoints;
        std::optional<bool> supportsExceptionFilterOptions;

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ResponseBody::ToJson();
            JsonSerializeOptional(o, supportsConfigurationDoneRequest);
            JsonSerializeOptional(o, supportsFunctionBreakpoints);
            JsonSerializeOptional(o, supportsConditionalBreakpoints);
            JsonSerializeOptional(o, supportsHitConditionalBreakpoints);
            JsonSerializeOptional(o, supportsEvaluateForHovers);
            JsonSerializeOptional(o, supportsStepBack);
            JsonSerializeOptional(o, supportsSetVariable);
            JsonSerializeOptional(o, supportsRestartFrame);
            JsonSerializeOptional(o, supportsGotoTargetsRequest);
            JsonSerializeOptional(o, supportsStepInTargetsRequest);
            JsonSerializeOptional(o, supportsCompletionsRequest);
            JsonSerializeOptional(o, supportsModulesRequest);
            JsonSerializeOptional(o, supportsRestartRequest);
            JsonSerializeOptional(o, supportsExceptionOptions);
            JsonSerializeOptional(o, supportsValueFormattingOptions);
            JsonSerializeOptional(o, supportsExceptionInfoRequest);
            JsonSerializeOptional(o, supportTerminateDebuggee);
            JsonSerializeOptional(o, supportSuspendDebuggee);
            JsonSerializeOptional(o, supportsDelayedStackTraceLoading);
            JsonSerializeOptional(o, supportsLoadedSourcesRequest);
            JsonSerializeOptional(o, supportsLogPoints);
            JsonSerializeOptional(o, supportsTerminateThreadsRequest);
            JsonSerializeOptional(o, supportsSetExpression);
            JsonSerializeOptional(o, supportsTerminateRequest);
            JsonSerializeOptional(o, supportsDataBreakpoints);
            JsonSerializeOptional(o, supportsReadMemoryRequest);
            JsonSerializeOptional(o, supportsWriteMemoryRequest);
            JsonSerializeOptional(o, supportsDisassembleRequest);
            JsonSerializeOptional(o, supportsCancelRequest);
            JsonSerializeOptional(o, supportsBreakpointLocationsRequest);
            JsonSerializeOptional(o, supportsClipboardContext);
            JsonSerializeOptional(o, supportsSteppingGranularity);
            JsonSerializeOptional(o, supportsInstructionBreakpoints);
            JsonSerializeOptional(o, supportsExceptionFilterOptions);
            return o;
        }
    });

    EmptyRequest(Launch, "launch");
    EmptyRequest(LoadedSources, "loadedSources");
    EmptyRequest(Modules, "modules");
    EmptyRequest(Next, "next");
    EmptyRequest(Pause, "pause");
    EmptyRequest(ReadMemory, "readMemory");
    EmptyRequest(Restart, "restart");
    EmptyRequest(RestartFrame, "restartFrame");
    EmptyRequest(ReverseContinue, "reverseContinue");

    RequestDefinition(Scopes, "scopes", {
        size_t frameId;
        explicit ScopesArguments(nlohmann::json & j) : RequestArguments(j), frameId(j["frameId"]) {}
    });
    RequestResponseDefinition(Scopes, "scopes", {
        std::vector<Scope> scopes;

        ScopesResponseBody(const std::vector<Scope>& scopes) : scopes(scopes) {}

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ResponseBody::ToJson();
            o["scopes"] = nlohmann::json::array();
            for (auto& t : scopes) {
                o["scopes"].push_back(t.ToJson());
            }
            return o;
        }
    });

    RequestDefinition(SetBreakpoints, "setBreakpoints", {
        Source source;
        std::optional<std::vector<SourceBreakpoint>> breakpoints;
        std::optional<bool> sourceModified;

        explicit SetBreakpointsArguments(nlohmann::json & j) : RequestArguments(j), source(j["source"]) {
            if (!j["breakpoints"].empty()) {
                std::vector<SourceBreakpoint> v;
                for (auto& i : j["breakpoints"]) {
                    v.emplace_back(i);
                }
                breakpoints = v;
            }
            JsonDeserializeOptional(j, sourceModified);
        }
    });
    RequestResponseDefinition(SetBreakpoints, "setBreakpoints", {
        std::vector<Breakpoint> breakpoints;
        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ResponseBody::ToJson();
            o["breakpoints"] = nlohmann::json::array();
            for (auto& bp : breakpoints) {
                o["breakpoints"].push_back(bp.ToJson());
            }
            return o;
        }
    });

    EmptyRequest(SetDataBreakpoints, "setDataBreakpoints");
    EmptyRequest(SetExceptionBreakpoints, "setExceptionBreakpoints");
    EmptyRequest(SetExpression, "setExpression");
    EmptyRequest(SetFunctionBreakpoints, "setFunctionBreakpoints");
    EmptyRequest(SetInstructionBreakpoints, "setInstructionBreakpoints");
    EmptyRequest(SetVariable, "setVariable");
    EmptyRequest(Source, "source");
    EmptyRequest(StackTrace, "stackTrace");

    RequestResponseDefinition(StackTrace, "stackTrace", {
        std::vector<StackFrame> stackFrames;
        std::optional<size_t> totalFrames;

        StackTraceResponseBody(std::vector<StackFrame> stackFrames)
            : stackFrames(stackFrames), totalFrames(stackFrames.size()) {}

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ResponseBody::ToJson();
            o["stackFrames"] = nlohmann::json::array();
            for (auto& t : stackFrames) {
                o["stackFrames"].push_back(t.ToJson());
            }
            JsonSerializeOptional(o, totalFrames);
            return o;
        }
    });

    EmptyRequest(StepBack, "stepBack");
    EmptyRequest(StepIn, "stepIn");
    EmptyRequest(StepInTargets, "stepInTargets");
    EmptyRequest(StepOut, "stepOut");

    RequestDefinition(Terminate, "terminate", {
        std::optional<bool> restart;
        explicit TerminateArguments(nlohmann::json & j) : RequestArguments(j), restart(j["restart"]) {}
    });

    EmptyRequest(TerminateThreads, "terminateThreads");
    RequestResponseDefinition(Threads, "threads", {
        std::vector<Thread> threads;

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ResponseBody::ToJson();
            o["threads"] = nlohmann::json::array();
            for (auto& t : threads) {
                o["threads"].push_back(t.ToJson());
            }
            return o;
        }
    });

    EmptyRequest(Threads, "threads");

    RequestDefinition(Variables, "variables", {
        size_t variablesReference;

        explicit VariablesArguments(nlohmann::json & j)
            : RequestArguments(j), variablesReference(j["variablesReference"]) {}
    });
    RequestResponseDefinition(Variables, "variables", {
        std::vector<Variable> variables;

        VariablesResponseBody(const std::vector<Variable>& v) : variables(v) {}

        [[nodiscard]] nlohmann::json ToJson() const override {
            auto o = ResponseBody::ToJson();
            o["variables"] = nlohmann::json::array();
            for (auto& t : variables) {
                o["variables"].push_back(t.ToJson());
            }
            return o;
        }
    });

    EmptyRequest(WriteMemory, "writeMemory");

#undef EmptyRequest
#undef RequestDefinition
}

#endif // ANGELSCRIPTDEBUGGER_REQUESTS_HPP


// end --- Requests.hpp --- 



void AngelscriptDebugger::Run(uint16_t port) {
    // Listen on port 9012
    _server = new asio::ip::tcp::acceptor(_ioContext, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port));
    std::thread([this]() { AcceptLoop(); }).detach();
}

template <typename... Args> std::string string_format(const std::string& format, Args... args) {
    int size_s = std::snprintf(nullptr, 0, format.c_str(), args...) + 1; // Extra space for '\0'
    if (size_s <= 0) {
        throw std::runtime_error("Error during formatting.");
    }
    auto size = static_cast<size_t>(size_s);
    auto buf = std::make_unique<char[]>(size);
    std::snprintf(buf.get(), size, format.c_str(), args...);
    return std::string(buf.get(), buf.get() + size - 1); // We don't want the '\0' inside
}

std::string AngelscriptDebugger::GetResolvedScriptPath(const char* scriptSection) {
    if (_scriptPath.has_value()) {
        return string_format(_scriptPath.value(), scriptSection);
    }
    return scriptSection;
}

void AngelscriptDebugger::on_line_callback(asIScriptContext* ctx, AngelscriptDebugger* d) {
    if (ctx->GetState() == asEXECUTION_SUSPENDED) {
        return;
    }
    const char* scriptSection = nullptr;
    int column = 0;
    int line = ctx->GetLineNumber(0, &column, &scriptSection);
    if (line == 0)
        return;

    auto resolvedScriptPath = d->GetResolvedScriptPath(scriptSection);
    auto sectionBreakpoints = d->_breakpoints.find(resolvedScriptPath);
    if (sectionBreakpoints != d->_breakpoints.end()) {
        if (sectionBreakpoints->second.contains(line)) {
            d->EnterBreakpoint(ctx, resolvedScriptPath, line);
        }
    }
}

size_t GetBreakpointHash(const std::string& section, size_t line) {
    using std::hash;
    using std::size_t;
    using std::string;

    return ((hash<string>()(section) ^ (hash<size_t>()(line) << 1)) >> 1);
}

void AngelscriptDebugger::on_exception_callback(asIScriptContext*, AngelscriptDebugger*) {
    return;
    //    const char* scriptSection = nullptr;
    //    int column = 0;
    //    int line = ctx->GetExceptionLineNumber(&column, &scriptSection);
    //    if (line == 0)
    //        return;
    //    auto b = Breakpoint{.Section = scriptSection, .Line = line};
    //    d->EnterBreakpoint(ctx, b);
}

void AngelscriptDebugger::EnterBreakpoint(asIScriptContext* ctx, const std::string& section, size_t line) {
    ctx->Suspend();
    _pausedContexts.push_back(ctx);

    auto* o = new DebugAdapterProtocol::StoppedEvent(
        new DebugAdapterProtocol::StoppedEventBody(GetBreakpointHash(section, line)));

    Send(o);
}

void AngelscriptDebugger::RegisterContext(asIScriptContext* ctx) {
    ctx->SetLineCallback(asFUNCTION(on_line_callback), this, asCALL_CDECL);
    ctx->SetExceptionCallback(asFUNCTION(on_exception_callback), this, asCALL_CDECL);
}

[[noreturn]] void AngelscriptDebugger::AcceptLoop() {
    while (true) {
        auto* client = new asio::ip::tcp::socket(_ioContext);
        _server->accept(*client);
        std::thread([this, client]() { ClientLoop(*client); }).detach();
    }
}

inline std::string trim(const std::string& s) {
    auto wsfront = std::find_if_not(s.begin(), s.end(), [](int c) { return std::isspace(c); });
    auto wsback = std::find_if_not(s.rbegin(), s.rend(), [](int c) { return std::isspace(c); }).base();
    return (wsback <= wsfront ? std::string() : std::string(wsfront, wsback));
}

void AngelscriptDebugger::ClientLoop(asio::ip::tcp::socket& client) {
    std::unordered_map<std::string, std::string> headers;
    bool in_header_mode = true;
    asio::streambuf buffer;
    while (true) {
        if (!client.is_open()) {
            break;
        }
        if (client.available() == 0 && buffer.size() == 0) {
            continue;
        }
        if (client.available() > 0 && buffer.size() == 0) {
            asio::error_code error;
            asio::read_until(client, buffer, "\r\n", error);
        }

        if (in_header_mode) {
            std::istream str(&buffer);
            std::string s;
            std::getline(str, s);
            if (s == "\r") {
                if (headers.contains("content-length")) {
                    in_header_mode = false;
                }
                continue;
            }
            auto delimiter = s.find(':');
            std::string key = trim(s.substr(0, delimiter));
            std::transform(key.begin(), key.end(), key.begin(), [](unsigned char c) { return std::tolower(c); });
            std::string value = trim(s.substr(delimiter + 1));
            headers[key] = value;
        } else {
            auto contentLength = headers["content-length"];
            auto size = (size_t)std::strtol(contentLength.c_str(), nullptr, 10);
            std::cout << "message length: " << size << std::endl;
            std::stringstream msg;
            int64_t to_fetch = (int64_t)size - (int64_t)buffer.size();
            auto to_read = std::min(buffer.size(), size);
            asio::streambuf::const_buffers_type constBuffer = buffer.data();
            std::copy(asio::buffers_begin(constBuffer), asio::buffers_end(constBuffer),
                      std::ostream_iterator<char>(msg));
            buffer.consume(to_read);
            if (to_fetch > 0) {
                std::vector<uint8_t> v(to_fetch);
                asio::read(client, asio::buffer(v), asio::transfer_exactly(to_fetch));
                msg << std::string(v.begin(), v.end());
            }

            auto m = msg.str();
            std::cout << "found message: " << m << std::endl;
            nlohmann::json j = nlohmann::json::parse(m.begin(), m.end());
            auto protocolMessage = DebugAdapterProtocol::ProtocolMessage::FromJson(j);
            OnMessage(&client, protocolMessage);

            in_header_mode = true;
        }
    }
}

void AngelscriptDebugger::Send(DebugAdapterProtocol::ProtocolMessage* msg) {
    auto body = msg->ToJson().dump();
    std::vector<uint8_t> vec(body.begin(), body.end());
    for (const auto& conn : _connections) {
        if (conn->is_open()) {
            asio::error_code ignored_error;
            asio::write(*conn, asio::buffer("Content-Length: " + std::to_string(vec.size()) + "\r\n\r\n"),
                        ignored_error);
            asio::write(*conn, asio::buffer(vec), ignored_error);
        }
    }
    std::cout << "Sent message: " << body << std::endl;
    delete msg;
}

void AngelscriptDebugger::Send(asio::ip::tcp::socket* client, DebugAdapterProtocol::ProtocolMessage* msg) {
    auto body = msg->ToJson().dump();
    std::vector<uint8_t> vec(body.begin(), body.end());
    if (client->is_open()) {
        asio::error_code ignored_error;
        asio::write(*client, asio::buffer("Content-Length: " + std::to_string(vec.size()) + "\r\n\r\n"), ignored_error);
        asio::write(*client, asio::buffer(vec), ignored_error);
    }
    std::cout << "Sent message: " << body << std::endl;
    delete msg;
}

void AngelscriptDebugger::OnMessage(asio::ip::tcp::socket* client, DebugAdapterProtocol::ProtocolMessage* msg) {
    if (msg->type == "request") {
        OnRequest(client, dynamic_cast<DebugAdapterProtocol::Request*>(msg));
    }
    delete msg;
}

const char empty[] = "";
void AngelscriptDebugger::OnRequest(asio::ip::tcp::socket* client, DebugAdapterProtocol::Request* msg) {
    if (msg->GetCommand() == "setBreakpoints") {
        auto* t = dynamic_cast<DebugAdapterProtocol::SetBreakpointsRequest*>(msg);
        auto& args = t->arguments.value();
        auto& path = args->source.path.value();
        auto* response = new DebugAdapterProtocol::SetBreakpointsResponse(msg->seq);
        auto* body = new DebugAdapterProtocol::SetBreakpointsResponseBody();
        response->body = body;
        std::unordered_set<size_t> sectionBreakpoints;
        if (args->breakpoints.has_value()) {
            for (auto& bp : args->breakpoints.value()) {
                sectionBreakpoints.insert(bp.line);
                body->breakpoints.emplace_back(GetBreakpointHash(path, bp.line), args->source, bp.line);
            }
        }
        _breakpoints[path] = sectionBreakpoints;
        Send(client, response);
    } else if (msg->GetCommand() == "initialize") {
        auto response = new DebugAdapterProtocol::InitializeResponse(msg->seq);
        auto* body = new DebugAdapterProtocol::InitializeResponseBody();
        response->body = body;
        Send(client, response);

        auto r = new DebugAdapterProtocol::InitializedEvent();
        Send(client, r);

    } else if (msg->GetCommand() == "disconnect") {
        auto response = new DebugAdapterProtocol::DefinedResponse<DebugAdapterProtocol::ResponseBody, empty>(msg->seq);
        Send(client, response);
        _connections.erase(client);
    } else if (msg->GetCommand() == "configurationDone") {
        _connections.insert(client);
        Send(client, new DebugAdapterProtocol::DefinedResponse<DebugAdapterProtocol::ResponseBody, empty>(msg->seq));
    } else if (msg->GetCommand() == "attach") {
        auto t = dynamic_cast<DebugAdapterProtocol::AttachRequest*>(msg);
        _scriptPath = t->arguments.value()->scriptPath;
        Send(client, new DebugAdapterProtocol::DefinedResponse<DebugAdapterProtocol::ResponseBody, empty>(msg->seq));
    } else if (msg->GetCommand() == "threads") {
        auto response = new DebugAdapterProtocol::ThreadsResponse(msg->seq);
        auto* body = new DebugAdapterProtocol::ThreadsResponseBody();
        response->body = body;
        body->threads = {DebugAdapterProtocol::Thread(0, "main")};
        Send(client, response);
    } else if (msg->GetCommand() == "continue") {
        Continue();
        Send(client, new DebugAdapterProtocol::DefinedResponse<DebugAdapterProtocol::ResponseBody, empty>(msg->seq));
    } else if (msg->GetCommand() == "stackTrace") {
        auto ctx = _pausedContexts[0];
        auto response = new DebugAdapterProtocol::StackTraceResponse(msg->seq);
        auto stackTrace = std::vector<DebugAdapterProtocol::StackFrame>();
        stackTrace.reserve(ctx->GetCallstackSize());
        for (asUINT i = 0; i < ctx->GetCallstackSize(); ++i) {
            auto func = ctx->GetFunction(i);
            const char* scriptSection = nullptr;
            int column = 0;
            int line = ctx->GetLineNumber(i, &column, &scriptSection);

            stackTrace.emplace_back(i, func->GetName(),
                                    DebugAdapterProtocol::Source(GetResolvedScriptPath(scriptSection)), line, column);
        }
        auto body = new DebugAdapterProtocol::StackTraceResponseBody(stackTrace);
        response->body = body;
        Send(client, response);
    } else if (msg->GetCommand() == "scopes") {
        auto t = dynamic_cast<DebugAdapterProtocol::ScopesRequest*>(msg);
        auto ctx = _pausedContexts[0];
        auto response = new DebugAdapterProtocol::ScopesResponse(msg->seq);
        auto scopes = std::vector<DebugAdapterProtocol::Scope>();
        auto frameId = t->arguments.value()->frameId;
        auto varCount = 0;
        for (int i = 0; i < ctx->GetVarCount(t->arguments.value()->frameId); ++i) {
            if (ctx->IsVarInScope(i, frameId)) {
                varCount++;
            }
        }
        _storedVariableReferences.emplace_back(std::make_unique<StackScope>(frameId, 0));
        scopes.emplace_back("Locals", _storedVariableReferences.size(), "locals", varCount);
        auto body = new DebugAdapterProtocol::ScopesResponseBody(scopes);
        response->body = body;
        Send(client, response);
    } else if (msg->GetCommand() == "variables") {
        auto t = dynamic_cast<DebugAdapterProtocol::VariablesRequest*>(msg);
        auto ctx = _pausedContexts[0];
        auto response = new DebugAdapterProtocol::VariablesResponse(msg->seq);
        auto variables = std::vector<DebugAdapterProtocol::Variable>();
        auto reference = t->arguments.value()->variablesReference - 1;
        if (reference >= _storedVariableReferences.size() || reference < 0)
            return;

        auto& variant = _storedVariableReferences[reference];
        auto e = ctx->GetEngine();
        // We currently can't use asIScriptContext::PushBack on suspended script contexts due to limitations in
        // angelscript. Hence we make a new contenxt
        auto c = e->CreateContext();

        if (holds_alternative<std::unique_ptr<StackScope>>(variant)) {
            auto frameId = std::get<std::unique_ptr<StackScope>>(variant)->StackLevel;
            // auto scopeType = get<StackScope>(variant).Type;
            auto varCount = ctx->GetVarCount(frameId);
            for (int i = 0; i < varCount; ++i) {
                if (!ctx->IsVarInScope(i, frameId)) {
                    continue;
                }
                auto name = ctx->GetVarName(i, frameId);
                auto type = ctx->GetVarTypeId(i, frameId);
                auto val = ctx->GetAddressOfVar(i, frameId);
                variables.push_back(ASVariableFormatter::GetAsDAPVariable(e, c, this, name, type, val));
            }
        } else {
            auto& v = std::get<std::unique_ptr<PointerVariable>>(variant);
            try {
                ASVariableFormatter::GetChildDAPVariables(variables, e, c, this, v->TypeID, v->Address);
            } catch (const std::exception& e) {
                std::cout << e.what() << std::endl;
            }
        }
        c->Release();
        auto body = new DebugAdapterProtocol::VariablesResponseBody(variables);
        response->body = body;
        Send(client, response);
    } else {
        Send(client, new DebugAdapterProtocol::DefinedResponse<DebugAdapterProtocol::ResponseBody, empty>(msg->seq));
    }
}


// end --- AngelscriptDebugger.cpp --- 



// begin --- BaseProtocol.cpp --- 



namespace DebugAdapterProtocol {
    ProtocolMessage* ProtocolMessage::FromJson(nlohmann::json& j) {
        auto t = j["type"];
        ProtocolMessage* msg;
        if (t == "request") {
            msg = Request::FromJson(j);
        } else if (t == "event") {
            throw std::logic_error("Unhandled type");
            // msg = Event::FromJson(j);
        } else if (t == "response") {
            msg = Response::FromJson(j);
        } else {
            throw std::logic_error("Unknown type");
        }
        msg->type = t;
        msg->seq = j["seq"];
        return msg;
    }

    template <IsRequestArguments T, const char* S> ProtocolMessage* DefinedRequest<T, S>::FromJson(nlohmann::json& j) {
        std::string c = j["command"];
        auto o = new DefinedRequest<T, S>();
        auto a = j["arguments"];
        if (!a.empty()) {
            o->arguments = dynamic_cast<T*>(new T(a));
        }
        return o;
    }
    ProtocolMessage* Request::FromJson(nlohmann::json& j) {
        std::string c = j["command"];
        // This is the worst, we need to fix this eventually.
        if (c == "attach")
            return AttachRequest::FromJson(j);
        if (c == "breakpointLocations")
            return BreakpointLocationsRequest::FromJson(j);
        if (c == "completions")
            return CompletionsRequest::FromJson(j);
        if (c == "configurationDone")
            return ConfigurationDoneRequest ::FromJson(j);
        if (c == "continue")
            return ContinueRequest ::FromJson(j);
        if (c == "dataBreakpointInfo")
            return DataBreakpointInfoRequest ::FromJson(j);
        if (c == "disassemble")
            return DisassembleRequest ::FromJson(j);
        if (c == "disconnect")
            return DisconnectRequest ::FromJson(j);
        if (c == "evaluate")
            return EvaluateRequest ::FromJson(j);
        if (c == "exceptionInfo")
            return ExceptionInfoRequest ::FromJson(j);
        if (c == "Goto")
            return GotoRequest ::FromJson(j);
        if (c == "GotoTargets")
            return GotoTargetsRequest ::FromJson(j);
        if (c == "initialize")
            return InitializeRequest ::FromJson(j);
        if (c == "launch")
            return LaunchRequest ::FromJson(j);
        if (c == "loadedSources")
            return LoadedSourcesRequest ::FromJson(j);
        if (c == "modules")
            return ModulesRequest ::FromJson(j);
        if (c == "next")
            return NextRequest ::FromJson(j);
        if (c == "pause")
            return PauseRequest ::FromJson(j);
        if (c == "readMemory")
            return ReadMemoryRequest ::FromJson(j);
        if (c == "restart")
            return RestartRequest ::FromJson(j);
        if (c == "restartFrame")
            return RestartFrameRequest ::FromJson(j);
        if (c == "reverseContinue")
            return RestartFrameRequest ::FromJson(j);
        if (c == "scopes")
            return ScopesRequest ::FromJson(j);
        if (c == "setBreakpoints")
            return SetBreakpointsRequest ::FromJson(j);
        if (c == "setDataBreakpoints")
            return SetDataBreakpointsRequest ::FromJson(j);
        if (c == "setExceptionBreakpoints")
            return SetExceptionBreakpointsRequest ::FromJson(j);
        if (c == "setExpression")
            return SetExpressionRequest ::FromJson(j);
        if (c == "setFunctionBreakpoints")
            return SetFunctionBreakpointsRequest ::FromJson(j);
        if (c == "setInstructionBreakpoints")
            return SetInstructionBreakpointsRequest ::FromJson(j);
        if (c == "setVariable")
            return SetVariableRequest ::FromJson(j);
        if (c == "source")
            return SourceRequest ::FromJson(j);
        if (c == "stackTrace")
            return StackTraceRequest ::FromJson(j);
        if (c == "stepBack")
            return StepBackRequest ::FromJson(j);
        if (c == "stepIn")
            return StepInRequest ::FromJson(j);
        if (c == "stepInTargets")
            return StepInTargetsRequest ::FromJson(j);
        if (c == "stepOut")
            return StepOutRequest ::FromJson(j);
        if (c == "terminate")
            return TerminateRequest ::FromJson(j);
        if (c == "terminateThreads")
            return TerminateThreadsRequest ::FromJson(j);
        if (c == "threads")
            return ThreadsRequest ::FromJson(j);
        if (c == "variables")
            return VariablesRequest ::FromJson(j);
        if (c == "writeMemory")
            return WriteMemoryRequest ::FromJson(j);
        throw std::logic_error("Unknown command");
    }
}

// end --- BaseProtocol.cpp --- 



// begin --- ASVariableFormatter.cpp --- 


#include <iostream>

DebugAdapterProtocol::Variable ASVariableFormatter::GetAsDAPVariable(asIScriptEngine* engine, asIScriptContext* ctx,
                                                                     AngelscriptDebugger* debugger,
                                                                     const std::string& name, int type, void* address) {
    if ((type & asTYPEID_OBJHANDLE) != 0) {
        address = *static_cast<void**>(address);
    }
    switch (type) {
        case asTYPEID_BOOL: {
            auto v = *static_cast<bool*>(address);
            return {name, v ? "true" : "false", "bool"};
        };

        case asTYPEID_INT8: {
            auto v = *static_cast<int8_t*>(address);
            return {name, std::to_string(v), "int8"};
        };
        case asTYPEID_INT16: {
            auto v = *static_cast<int16_t*>(address);
            return {name, std::to_string(v), "int16"};
        }
        case asTYPEID_INT32: {
            auto v = *static_cast<int32_t*>(address);
            return {name, std::to_string(v), "int32"};
        };
        case asTYPEID_INT64: {
            auto v = *static_cast<int64_t*>(address);
            return {name, std::to_string(v), "int64"};
        };

        case asTYPEID_UINT8: {
            auto v = *static_cast<uint8_t*>(address);
            return {name, std::to_string(v), "uint8"};
        };
        case asTYPEID_UINT16: {
            auto v = *static_cast<uint16_t*>(address);
            return {name, std::to_string(v), "uint16"};
        };
        case asTYPEID_UINT32: {
            auto v = *static_cast<uint32_t*>(address);
            return {name, std::to_string(v), "uint32"};
        };
        case asTYPEID_UINT64: {
            auto v = *static_cast<uint64_t*>(address);
            return {name, std::to_string(v), "uint64"};
        };

        case asTYPEID_FLOAT: {
            auto v = *static_cast<float*>(address);
            return {name, std::to_string(v), "float"};
        };
        case asTYPEID_DOUBLE: {
            auto v = *static_cast<double*>(address);
            return {name, std::to_string(v), "double"};
        };

        default: break;
    }
    if ((type & asTYPEID_SCRIPTOBJECT) != 0) {
        auto* obj = static_cast<asIScriptObject*>(address);
        std::string typeName = engine->GetTypeInfoById(type)->GetName();
        if (obj == nullptr) {
            return DebugAdapterProtocol::Variable::FromNull(name, typeName);
        }
        auto typeData = obj->GetObjectType();
        std::string stringified = typeName + "(*" + std::to_string((size_t)address) + ")";
        auto stringifiedFunc = typeData->GetMethodByDecl("string opImplConv()");
        if (stringifiedFunc != nullptr) {
            ctx->PushState();
            ctx->Prepare(stringifiedFunc);
            ctx->SetObject(address);
            if (ctx->Execute() == asEXECUTION_FINISHED) {
                stringified = *(std::string*)ctx->GetAddressOfReturnValue();
            }
            ctx->PopState();
        }

        size_t reference = 0;
        if (obj->GetPropertyCount() > 0) {
            std::cout << "Storing scriptObj " << name << std::endl;
            obj->AddRef();
            reference = debugger->StoreVariable(address, type, [](void* a) {
                if (a != nullptr) {
                    ((asIScriptObject*)a)->Release();
                }
            });
        }
        return DebugAdapterProtocol::Variable::FromPointer(name, typeName, stringified, reference);
    }
    // TODO: Caching of type id
    if (type == engine->GetTypeIdByDecl("string")) {
        auto* str = static_cast<std::string*>(address);
        if (str == nullptr) {
            return DebugAdapterProtocol::Variable::FromNull(name, "string");
        }
        return DebugAdapterProtocol::Variable::FromString(name, "\"" + *str + "\"");
    }
    auto* typeData = engine->GetTypeInfoById(type);
    if ((typeData->GetFlags() & asOBJ_ENUM) != 0) {
        const auto* enumValue = typeData->GetEnumValueByIndex(*static_cast<int32_t*>(address), nullptr);
        return {name, enumValue, typeData->GetName()};
    }

    if (address == nullptr) {
        return DebugAdapterProtocol::Variable::FromNull(name, typeData->GetName());
    }

    std::string stringified = std::string(typeData->GetName()) + "(*" + std::to_string((size_t)address) + ")";
    auto stringifiedFunc = typeData->GetMethodByDecl("string opImplConv()");
    if (stringifiedFunc != nullptr) {
        ctx->Prepare(stringifiedFunc);
        ctx->SetObject(*(void**)address);
        if (ctx->Execute() == asEXECUTION_FINISHED) {
            stringified = "\"" + *(std::string*)ctx->GetAddressOfReturnValue() + "\"";
        }
    }

    size_t reference = 0;
    size_t varCount = typeData->GetPropertyCount();
    for (asUINT i = 0; i < typeData->GetMethodCount(); ++i) {
        if (typeData->GetMethodByIndex(i, true)->IsProperty()) {
            varCount++;
        }
    }
    if (varCount > 0) {

        // If we want to re-use an object, we need to make sure to add a reference to it, so it doesn't get accidentally
        // cleaned up. This is currently somewhat clunky, we look up the AddRef behaviour, and execute that in a new
        // context.
        auto b = static_cast<asEBehaviours>(-1);
        asIScriptFunction* releaseFunc = nullptr;
        bool hasAddRef = false;
        for (asUINT i = 0; i < typeData->GetBehaviourCount(); ++i) {
            auto* f = typeData->GetBehaviourByIndex(i, &b);
            if (b == asBEHAVE_ADDREF) {
                // If we push state or re-use the active context, we might lose the reference to the object we have.
                // To resolve this we need a new context.
                auto* c = ctx->GetEngine()->CreateContext();
                assert(c->Prepare(f) >= 0);
                assert(c->SetObject(address) >= 0);
                assert(c->Execute() >= 0);
                c->Release();
                hasAddRef = true;
            }
            if (b == asBEHAVE_RELEASE) {
                releaseFunc = f;
            }
        }
        if (!hasAddRef) {
            releaseFunc = nullptr;
        }

        reference = debugger->StoreVariable(address, type, [engine, releaseFunc](void* a) {
            if (releaseFunc != nullptr) {
                auto* c = engine->CreateContext();
                c->Prepare(releaseFunc);
                c->SetObject(a);
                c->Execute();
                c->Release();
            }
        });
    }
    return DebugAdapterProtocol::Variable::FromPointer(name, typeData->GetName(), stringified, reference);
}
void ASVariableFormatter::GetChildDAPVariables(std::vector<DebugAdapterProtocol::Variable>& vars,
                                               asIScriptEngine* engine, asIScriptContext* ctx,
                                               AngelscriptDebugger* debugger, int type, void* address) {
    if (address == nullptr) {
        return;
    }

    if ((type & asTYPEID_SCRIPTOBJECT) != 0) {
        auto* obj = static_cast<asIScriptObject*>(address);
        for (asUINT i = 0; i < obj->GetPropertyCount(); ++i) {
            const auto* propertyName = obj->GetPropertyName(i);
            auto propertyType = obj->GetPropertyTypeId(i);
            auto* propertyAddress = obj->GetAddressOfProperty(i);
            vars.push_back(GetAsDAPVariable(engine, ctx, debugger, propertyName, propertyType, propertyAddress));
        }
        return;
    }

    auto* typeData = engine->GetTypeInfoById(type);
    for (asUINT i = 0; i < typeData->GetPropertyCount(); ++i) {
        const char* propertyName = nullptr;
        int propertyType = 0;
        int offset = 0;

        typeData->GetProperty(i, &propertyName, &propertyType, nullptr, nullptr, &offset);
        vars.push_back(
            GetAsDAPVariable(engine, ctx, debugger, propertyName, propertyType, (void*)((size_t)address + offset)));
    }
    for (asUINT i = 0; i < typeData->GetMethodCount(); ++i) {
        auto func = typeData->GetMethodByIndex(i, true);
        if (func->IsProperty()) {
            assert(ctx->Prepare(func) >= 0);
            ctx->SetObject(address);
            auto state = ctx->Execute();
            if (state == asEXECUTION_FINISHED) {
                auto name = std::string(func->GetName()).substr(4);
                vars.push_back(GetAsDAPVariable(engine, ctx, debugger, name, func->GetReturnTypeId(),
                                                ctx->GetAddressOfReturnValue()));
            }
        }
    }
}


// end --- ASVariableFormatter.cpp --- 

