#ifndef ANGELSCRIPTDEBUGGER_ANGELSCRIPTDEBUGGER_HPP
#define ANGELSCRIPTDEBUGGER_ANGELSCRIPTDEBUGGER_HPP
#include <angelscript.h>
#include <iostream>
#include <unordered_set>
#include <variant>
#include "asio.hpp"
#include "Breakpoint.hpp"
#include "DebugAdapterProtocol/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);

    void Next(asIScriptContext* ctx);
    void StepInto(asIScriptContext* ctx);
    void StepOut(asIScriptContext* ctx);

    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;

    asIScriptContext* _nextContext;
    bool _next;
    size_t _nextDepth;
    bool _stepInto;
    bool _stepOut;

    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
