#ifndef ANGELSCRIPTDEBUGGER_TYPES_HPP
#define ANGELSCRIPTDEBUGGER_TYPES_HPP
#include <optional>
#include <string>
#include "Utils.hpp"

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
