/*
    SPDX-FileCopyrightText: 2022 Héctor Mesa Jiménez <wmj.py@gmx.com>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/
#pragma once

#include <QHash>
#include <QJsonValue>
#include <QList>
#include <QString>
#include <optional>

class QJsonObject;

namespace dap
{
struct Message {
    /**
     * @brief id unique identifier for the message
     */
    int id;
    /**
     * @brief format A format string for the message.
     *
     * Embedded variables have the form '{name}'. If variable
     * name starts with an underscore character, the variable
     * does not contain user data (PII) and can be safely used for
     * telemetry purposes.
     */
    QString format;
    /**
     * @brief variables An object used as a dictionary for looking
     * up the variables in the format string.
     */
    std::optional<QHash<QString, QString>> variables;
    /**
     * @brief sendTelemetry If true send telemetry
     */
    std::optional<bool> sendTelemetry;
    /**
     * @brief showUser If true show user
     */
    std::optional<bool> showUser;
    /**
     * @brief showUser An optional url where additional information
     * about this message can be found.
     */
    std::optional<QString> url;
    /**
     * @brief urlLabel An optional label that is presented to the user as the UI
     * for opening the url.
     */
    std::optional<QString> urlLabel;

    Message() = default;
    Message(const QJsonObject &body);
};

struct Response {
    int request_seq;
    bool success;
    QString command;
    QString message;
    QJsonValue body;
    std::optional<Message> errorBody;

    Response() = default;
    Response(const QJsonObject &msg);

    bool isCancelled() const;
};

struct ProcessInfo {
    /**
     * @brief name the logical name of the process
     *
     * This is usually the full path to process's executable file.
     */
    QString name;
    /**
     * @brief systemProcessId the system process id of the debugged process
     *
     * This property will be missing for non-system processes.
     */
    std::optional<int> systemProcessId;
    /**
     * @brief isLocalProcess if true, the process is running on the same computer as DA
     */
    std::optional<bool> isLocalProcess;
    /**
     * @brief startMethod describes how the debug engine started debugging this process.
     */
    std::optional<QString> startMethod;
    /**
     * @brief pointerSize the sized of a pointer or address for this process, in bits.
     *
     * This value may be used by clients when formatting addresses for display.
     */
    std::optional<int> pointerSize;

    ProcessInfo() = default;
    ProcessInfo(const QJsonObject &body);
};

struct MessageContext {
    virtual ~MessageContext() = default;
    virtual QString toRemote(const QUrl &) = 0;
    virtual QUrl toLocal(const QString &) = 0;
};

struct Checksum {
    QString checksum;
    QString algorithm;

    Checksum() = default;
    Checksum(const QJsonObject &body);
    bool operator==(const Checksum &r) const;

    QJsonObject toJson() const;
};

struct Source {
    QString name;
    QUrl path;
    std::optional<int> sourceReference;
    std::optional<QString> presentationHint;
    QString origin;
    QList<Source> sources;
    QJsonValue adapterData;
    QList<Checksum> checksums;

    static QString referenceScheme();
    QUrl unifiedId() const;
    static QUrl getUnifiedId(const QUrl &path, std::optional<int> sourceReference);

    Source() = default;
    Source(const QJsonObject &body, MessageContext &);
    Source(const QUrl &path);
    bool operator==(const Source &r) const;

    QJsonObject toJson(MessageContext &ctx) const;
};

struct SourceContent {
    QString content;
    std::optional<QString> mimeType;

    SourceContent() = default;
    SourceContent(const QJsonObject &body);
    SourceContent(const QString &path);
};

struct SourceBreakpoint {
    int line;
    std::optional<int> column;
    /**
     * An optional expression for conditional breakpoints.
     * It is only honored by a debug adapter if the capability
     * 'supportsConditionalBreakpoints' is true.
     */
    std::optional<QString> condition;
    /**
     * An optional expression that controls how many hits of the breakpoint are
     * ignored.
     * The backend is expected to interpret the expression as needed.
     * The attribute is only honored by a debug adapter if the capability
     * 'supportsHitConditionalBreakpoints' is true.
     */
    std::optional<QString> hitCondition;
    /**
     * If this attribute exists and is non-empty, the backend must not 'break'
     * (stop)
     * but log the message instead. Expressions within {} are interpolated.
     * The attribute is only honored by a debug adapter if the capability
     * 'supportsLogPoints' is true.
     */
    std::optional<QString> logMessage;

    SourceBreakpoint() = default;
    SourceBreakpoint(const QJsonObject &body);
    explicit SourceBreakpoint(const int line);

    QJsonObject toJson() const;
};

struct Breakpoint {
    /**
     * An optional identifier for the breakpoint. It is needed if breakpoint
     * events are used to update or remove breakpoints.
     */
    std::optional<int> id;
    /**
     * If true breakpoint could be set (but not necessarily at the desired
     * location).
     */
    bool verified = false;
    /**
     * An optional message about the state of the breakpoint.
     * This is shown to the user and can be used to explain why a breakpoint could
     * not be verified.
     */
    std::optional<QString> message;
    std::optional<Source> source;
    /**
     * The start line of the actual range covered by the breakpoint.
     */
    std::optional<int> line;
    std::optional<int> column;
    std::optional<int> endLine;
    std::optional<int> endColumn;
    /**
     * An optional memory reference to where the breakpoint is set.
     */
    std::optional<QString> instructionReference;
    /**
     * An optional offset from the instruction reference.
     * This can be negative.
     */
    std::optional<int> offset;

    Breakpoint() = default;
    Breakpoint(const QJsonObject &body, MessageContext &);
    Breakpoint(const int line);

    bool operator==(const dap::Breakpoint &r) const;
};

struct FunctionBreakpoint {
    QString function;
    std::optional<QString> condition;
    std::optional<QString> hitCondition;

    FunctionBreakpoint() = default;
    FunctionBreakpoint(const QJsonObject &body);
    FunctionBreakpoint(const QString &functionName);

    QJsonObject toJson() const;
};

class Output
{
    Q_GADGET
public:
    enum class Category {
        Console,
        Important,
        Stdout,
        Stderr,
        Telemetry,
        Unknown
    };

    Q_ENUM(Category)

    enum class Group {
        Start,
        StartCollapsed,
        End,
    };

    Category category;
    QString output;
    std::optional<Group> group;
    std::optional<int> variablesReference;
    std::optional<Source> source;
    std::optional<int> line;
    std::optional<int> column;
    QJsonValue data;

    Output() = default;
    Output(const QJsonObject &body, MessageContext &ctx);
    Output(const QString &output, const Category &category);

    bool isSpecialOutput() const;
};

struct ExceptionBreakpointsFilter {
    /**
     * The internal ID of the filter option. This value is passed to the
     * `setExceptionBreakpoints` request.
     */
    QString filter;

    /**
     * The name of the filter option. This is shown in the UI.
     */
    QString label;

    /**
     * A help text providing additional information about the exception filter.
     * This string is typically shown as a hover and can be translated.
     */
    std::optional<QString> description;

    /**
     * Initial value of the filter option. If not specified a value false is
     * assumed.
     */
    std::optional<bool> defaultValue;

    /**
     * Controls whether a condition can be specified for this filter option. If
     * false or missing, a condition can not be set.
     */
    std::optional<bool> supportsCondition;

    /**
     * A help text providing information about the condition. This string is shown
     * as the placeholder text for a text box and can be translated.
     */
    std::optional<QString> conditionDescription;

    ExceptionBreakpointsFilter() = default;
    ExceptionBreakpointsFilter(const QJsonObject &body);

    [[nodiscard]] QString toString() const;
};

struct Capabilities {
    bool supportsConfigurationDoneRequest;
    bool supportsFunctionBreakpoints;
    bool supportsConditionalBreakpoints;
    bool supportsHitConditionalBreakpoints;
    bool supportsLogPoints;
    bool supportsModulesRequest;
    bool supportsTerminateRequest;
    bool supportTerminateDebuggee;
    bool supportsGotoTargetsRequest;
    std::optional<QList<ExceptionBreakpointsFilter>> exceptionBreakpointFilters;

    Capabilities() = default;
    Capabilities(const QJsonObject &body);
};

struct ThreadEvent {
    QString reason;
    int threadId;

    ThreadEvent() = default;
    ThreadEvent(const QJsonObject &body);
};

struct Module {
    std::optional<int> id_int;
    std::optional<QString> id_str;
    QString name;
    std::optional<QString> path;
    std::optional<bool> isOptimized;
    std::optional<bool> isUserCode;
    std::optional<QString> version;
    std::optional<QString> symbolStatus;
    std::optional<QString> symbolFilePath;
    std::optional<QString> dateTimeStamp;
    std::optional<QString> addressRange;

    Module() = default;
    Module(const QJsonObject &body);
};

struct ModulesInfo {
    QList<Module> modules;
    std::optional<int> totalModules;

    ModulesInfo() = default;
    ModulesInfo(const QJsonObject &body);
};

struct ModuleEvent {
    QString reason;
    Module module;

    ModuleEvent() = default;
    ModuleEvent(const QJsonObject &body);
};

struct StoppedEvent {
    QString reason;
    std::optional<QString> description;
    /**
     * @brief threadId The thread which was stopped.
     */
    std::optional<int> threadId;
    /**
     * @brief preserverFocusHint A value of true hints to the frontend that
     * this event should not change the focus
     */
    std::optional<bool> preserveFocusHint;
    /**
     * @brief text Additional information.
     */
    std::optional<QString> text;
    /**
     * @brief allThreadsStopped if true, a DA can announce
     * that all threads have stopped.
     * - The client should use this information to enable that all threads can be
     *  expanded to access their stacktraces.
     * - If the attribute is missing or false, only the thread with the given threadId
     *   can be expanded.
     */
    std::optional<bool> allThreadsStopped;
    /**
     * @brief hitBreakpointIds ids of the breakpoints that triggered the event
     */
    std::optional<QList<int>> hitBreakpointIds;

    StoppedEvent() = default;
    StoppedEvent(const QJsonObject &body);
};

struct ContinuedEvent {
    int threadId;
    /**
     * If 'allThreadsContinued' is true, a debug adapter can announce that all
     * threads have continued.
     */
    std::optional<bool> allThreadsContinued;

    ContinuedEvent() = default;
    ContinuedEvent(int threadId, bool allThreadsContinued);
    ContinuedEvent(const QJsonObject &body);
};

struct BreakpointEvent {
    QString reason;
    Breakpoint breakpoint;

    BreakpointEvent() = default;
    BreakpointEvent(const QJsonObject &body, MessageContext &);
};

struct Thread {
    int id;
    QString name;

    Thread() = default;
    Thread(const QJsonObject &body);
    explicit Thread(const int id);

    static QList<Thread> parseList(const QJsonArray &threads);
};

struct StackFrame {
    int id;
    QString name;
    std::optional<Source> source;
    int line;
    int column;
    std::optional<int> endLine;
    std::optional<int> endColumn;
    std::optional<bool> canRestart;
    std::optional<QString> instructionPointerReference;
    std::optional<int> moduleId_int;
    std::optional<QString> moduleId_str;
    std::optional<QString> presentationHint;

    StackFrame() = default;
    StackFrame(const QJsonObject &body, MessageContext &ctx);
};

struct StackTraceInfo {
    QList<StackFrame> stackFrames;
    std::optional<int> totalFrames;

    StackTraceInfo() = default;
    StackTraceInfo(const QJsonObject &body, MessageContext &ctx);
};

struct Scope {
    QString name;
    std::optional<QString> presentationHint;
    int variablesReference;
    std::optional<int> namedVariables;
    std::optional<int> indexedVariables;
    std::optional<bool> expensive;
    std::optional<Source> source;
    std::optional<int> line;
    std::optional<int> column;
    std::optional<int> endLine;
    std::optional<int> endColumn;

    Scope() = default;
    Scope(const QJsonObject &body, MessageContext &ctx);
    Scope(int variablesReference, QString name);

    static QList<Scope> parseList(const QJsonArray &scopes, MessageContext &ctx);
};

struct Variable {
    enum Type {
        Indexed = 1,
        Named = 2,
        Both = 3
    };

    QString name;
    QString value;
    std::optional<QString> type;
    /**
     * @brief evaluateName Optional evaluatable name of this variable which can be
     * passed to the EvaluateRequest to fetch the variable's value
     */
    std::optional<QString> evaluateName;
    /**
     * @brief variablesReference if > 0, its children can be retrieved by VariablesRequest
     */
    int variablesReference;
    /**
     * @brief namedVariables number of named child variables
     */
    std::optional<int> namedVariables;
    /**
     * @brief indexedVariables number of indexed child variables
     */
    std::optional<int> indexedVariables;
    /**
     * @brief memoryReference optional memory reference for the variable if the
     * variable represents executable code, such as a function pointer.
     * Requires 'supportsMemoryReferences'
     */
    std::optional<QString> memoryReference;

    /**
     * the value has changed since the last time
     */
    std::optional<bool> valueChanged;

    Variable() = default;
    Variable(const QJsonObject &body);
    Variable(const QString &name, const QString &value, const int reference = 0);

    static QList<Variable> parseList(const QJsonArray &variables);
};

struct EvaluateInfo {
    QString result;
    std::optional<QString> type;
    int variablesReference;
    std::optional<int> namedVariables;
    std::optional<int> indexedVariables;
    std::optional<QString> memoryReference;

    EvaluateInfo();
    EvaluateInfo(const QJsonObject &body);
};

struct GotoTarget {
    int id;
    QString label;
    int line;
    std::optional<int> column;
    std::optional<int> endLine;
    std::optional<int> endColumn;
    std::optional<QString> instructionPointerReference;

    GotoTarget() = default;
    GotoTarget(const QJsonObject &body);

    static QList<GotoTarget> parseList(const QJsonArray &variables);
};

/**
 * This request is sent from the debug adapter to the client to run a command in a terminal.
 */
struct RunInTerminalRequestArguments {
    /**
     * Title of the terminal.
     *
     * title?: string;
     */
    std::optional<QString> title;
    /**
     * Working directory for the command. For non-empty, valid paths this
     * typically results in execution of a change directory command.
     *
     * cwd: string;
     */
    QString cwd;
    /**
     * List of arguments. The first argument is the command to run.
     *
     * args: string[];
     */
    QStringList args;
    /**
     * Environment key-value pairs that are added to or removed from the default
     * environment.
     */
    std::optional<QHash<QString, std::optional<QString>>> env;

    RunInTerminalRequestArguments() = default;
    RunInTerminalRequestArguments(const QJsonObject &body);
};
}
