//===-- DAP.cpp -------------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#include "DAP.h"
#include "CommandPlugins.h"
#include "DAPLog.h"
#include "EventHelper.h"
#include "ExceptionBreakpoint.h"
#include "Handler/RequestHandler.h"
#include "Handler/ResponseHandler.h"
#include "JSONUtils.h"
#include "LLDBUtils.h"
#include "OutputRedirector.h"
#include "Protocol/ProtocolBase.h"
#include "Protocol/ProtocolEvents.h"
#include "Protocol/ProtocolRequests.h"
#include "Protocol/ProtocolTypes.h"
#include "ProtocolUtils.h"
#include "Transport.h"
#include "lldb/API/SBBreakpoint.h"
#include "lldb/API/SBCommandInterpreter.h"
#include "lldb/API/SBEvent.h"
#include "lldb/API/SBLanguageRuntime.h"
#include "lldb/API/SBListener.h"
#include "lldb/API/SBMutex.h"
#include "lldb/API/SBProcess.h"
#include "lldb/API/SBStream.h"
#include "lldb/Host/JSONTransport.h"
#include "lldb/Host/MainLoop.h"
#include "lldb/Host/MainLoopBase.h"
#include "lldb/Utility/Status.h"
#include "lldb/lldb-defines.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-types.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/Chrono.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_ostream.h"
#include <algorithm>
#include <cassert>
#include <chrono>
#include <condition_variable>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <utility>
#include <variant>

#if defined(_WIN32)
#define NOMINMAX
#include <fcntl.h>
#include <io.h>
#include <windows.h>
#else
#include <unistd.h>
#endif

using namespace lldb_dap;
using namespace lldb_dap::protocol;
using namespace lldb_private;

namespace {
#ifdef _WIN32
const char DEV_NULL[] = "nul";
#else
const char DEV_NULL[] = "/dev/null";
#endif
} // namespace

namespace lldb_dap {

static std::string GetStringFromStructuredData(lldb::SBStructuredData &data,
                                               const char *key) {
  lldb::SBStructuredData keyValue = data.GetValueForKey(key);
  if (!keyValue)
    return std::string();

  const size_t length = keyValue.GetStringValue(nullptr, 0);

  if (length == 0)
    return std::string();

  std::string str(length + 1, 0);
  keyValue.GetStringValue(&str[0], length + 1);
  return str;
}

static uint64_t GetUintFromStructuredData(lldb::SBStructuredData &data,
                                          const char *key) {
  lldb::SBStructuredData keyValue = data.GetValueForKey(key);

  if (!keyValue.IsValid())
    return 0;
  return keyValue.GetUnsignedIntegerValue();
}

/// Return string with first character capitalized.
static std::string capitalize(llvm::StringRef str) {
  if (str.empty())
    return "";
  return ((llvm::Twine)llvm::toUpper(str[0]) + str.drop_front()).str();
}

llvm::StringRef DAP::debug_adapter_path = "";

DAP::DAP(Log &log, const ReplMode default_repl_mode,
         std::vector<std::string> pre_init_commands, bool no_lldbinit,
         llvm::StringRef client_name, DAPTransport &transport, MainLoop &loop)
    : log(log), transport(transport), broadcaster("lldb-dap"),
      progress_event_reporter(
          [&](const ProgressEvent &event) { SendJSON(event.ToJSON()); }),
      repl_mode(default_repl_mode), no_lldbinit(no_lldbinit),
      m_client_name(client_name), m_loop(loop) {
  configuration.preInitCommands = std::move(pre_init_commands);
  RegisterRequests();
}

void DAP::PopulateExceptionBreakpoints() {
  if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeC_plus_plus)) {
    exception_breakpoints.emplace_back(*this, "cpp_catch", "C++ Catch",
                                       lldb::eLanguageTypeC_plus_plus,
                                       eExceptionKindCatch);
    exception_breakpoints.emplace_back(*this, "cpp_throw", "C++ Throw",
                                       lldb::eLanguageTypeC_plus_plus,
                                       eExceptionKindThrow);
  }

  if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeObjC)) {
    exception_breakpoints.emplace_back(*this, "objc_catch", "Objective-C Catch",
                                       lldb::eLanguageTypeObjC,
                                       eExceptionKindCatch);
    exception_breakpoints.emplace_back(*this, "objc_throw", "Objective-C Throw",
                                       lldb::eLanguageTypeObjC,
                                       eExceptionKindThrow);
  }

  if (lldb::SBDebugger::SupportsLanguage(lldb::eLanguageTypeSwift)) {
    exception_breakpoints.emplace_back(*this, "swift_catch", "Swift Catch",
                                       lldb::eLanguageTypeSwift,
                                       eExceptionKindCatch);
    exception_breakpoints.emplace_back(*this, "swift_throw", "Swift Throw",
                                       lldb::eLanguageTypeSwift,
                                       eExceptionKindThrow);
  }

  // Besides handling the hardcoded list of languages from above, we try to find
  // any other languages that support exception breakpoints using the SB API.
  for (int raw_lang = lldb::eLanguageTypeUnknown;
       raw_lang < lldb::eNumLanguageTypes; ++raw_lang) {
    lldb::LanguageType lang = static_cast<lldb::LanguageType>(raw_lang);

    // We first discard any languages already handled above.
    if (lldb::SBLanguageRuntime::LanguageIsCFamily(lang) ||
        lang == lldb::eLanguageTypeSwift)
      continue;

    if (!lldb::SBDebugger::SupportsLanguage(lang))
      continue;

    const char *name = lldb::SBLanguageRuntime::GetNameForLanguageType(lang);
    if (!name)
      continue;
    std::string raw_lang_name = name;
    std::string capitalized_lang_name = capitalize(name);

    if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnThrow(lang)) {
      const char *raw_throw_keyword =
          lldb::SBLanguageRuntime::GetThrowKeywordForLanguage(lang);
      std::string throw_keyword =
          raw_throw_keyword ? raw_throw_keyword : "throw";

      exception_breakpoints.emplace_back(
          *this, raw_lang_name + "_" + throw_keyword,
          capitalized_lang_name + " " + capitalize(throw_keyword), lang,
          eExceptionKindThrow);
    }

    if (lldb::SBLanguageRuntime::SupportsExceptionBreakpointsOnCatch(lang)) {
      const char *raw_catch_keyword =
          lldb::SBLanguageRuntime::GetCatchKeywordForLanguage(lang);
      std::string catch_keyword =
          raw_catch_keyword ? raw_catch_keyword : "catch";

      exception_breakpoints.emplace_back(
          *this, raw_lang_name + "_" + catch_keyword,
          capitalized_lang_name + " " + capitalize(catch_keyword), lang,
          eExceptionKindCatch);
    }
  }
}

ExceptionBreakpoint *DAP::GetExceptionBreakpoint(llvm::StringRef filter) {
  for (auto &bp : exception_breakpoints) {
    if (bp.GetFilter() == filter)
      return &bp;
  }
  return nullptr;
}

ExceptionBreakpoint *DAP::GetExceptionBreakpoint(const lldb::break_id_t bp_id) {
  for (auto &bp : exception_breakpoints) {
    if (bp.GetID() == bp_id)
      return &bp;
  }
  return nullptr;
}

llvm::Error DAP::ConfigureIO(std::FILE *overrideOut, std::FILE *overrideErr) {
  in = lldb::SBFile(std::fopen(DEV_NULL, "r"), /*transfer_ownership=*/true);

  if (auto Error = out.RedirectTo(overrideOut, [this](llvm::StringRef output) {
        SendOutput(OutputType::Console, output);
      }))
    return Error;

  if (auto Error = err.RedirectTo(overrideErr, [this](llvm::StringRef output) {
        SendOutput(OutputType::Console, output);
      }))
    return Error;

  return llvm::Error::success();
}

void DAP::StopEventHandlers() {
  event_thread_sp.reset();

  // Clean up expired event threads from the session manager.
  DAPSessionManager::GetInstance().ReleaseExpiredEventThreads();

  // Still handle the progress thread normally since it's per-DAP instance.
  if (progress_event_thread.joinable()) {
    broadcaster.BroadcastEventByType(eBroadcastBitStopProgressThread);
    progress_event_thread.join();
  }
}

// Serialize the JSON value into a string and send the JSON packet to
// the "out" stream.
void DAP::SendJSON(const llvm::json::Value &json) {
  // FIXME: Instead of parsing the output message from JSON, pass the `Message`
  // as parameter to `SendJSON`.
  Message message;
  llvm::json::Path::Root root;
  if (!fromJSON(json, message, root)) {
    DAP_LOG_ERROR(log, root.getError(), "encoding failed: {0}");
    return;
  }
  Send(message);
}

Id DAP::Send(const Message &message) {
  std::lock_guard<std::mutex> guard(call_mutex);
  Message msg = std::visit(
      [this](auto &&msg) -> Message {
        if (msg.seq == kCalculateSeq) {
          seq++;
          msg.seq = seq;
        }
        assert(msg.seq > 0 && "message sequence must be greater than zero.");
        return msg;
      },
      Message(message));

  if (const protocol::Event *event = std::get_if<protocol::Event>(&msg)) {
    if (llvm::Error err = transport.Send(*event))
      DAP_LOG_ERROR(log, std::move(err), "sending event failed: {0}");
    return event->seq;
  }

  if (const Request *req = std::get_if<Request>(&msg)) {
    if (llvm::Error err = transport.Send(*req))
      DAP_LOG_ERROR(log, std::move(err), "sending request failed: {0}");
    return req->seq;
  }

  if (const Response *resp = std::get_if<Response>(&msg)) {
    // FIXME: After all the requests have migrated from LegacyRequestHandler >
    // RequestHandler<> this should be handled in RequestHandler<>::operator().
    // If the debugger was interrupted, convert this response into a
    // 'cancelled' response because we might have a partial result.
    llvm::Error err = (debugger.InterruptRequested())
                          ? transport.Send({
                                /*request_seq=*/resp->request_seq,
                                /*command=*/resp->command,
                                /*success=*/false,
                                /*message=*/eResponseMessageCancelled,
                                /*body=*/std::nullopt,
                                /*seq=*/resp->seq,
                            })
                          : transport.Send(*resp);
    if (err)
      DAP_LOG_ERROR(log, std::move(err), "sending response failed: {0}");
    return resp->seq;
  }

  llvm_unreachable("Unexpected message type");
}

// "OutputEvent": {
//   "allOf": [ { "$ref": "#/definitions/Event" }, {
//     "type": "object",
//     "description": "Event message for 'output' event type. The event
//                     indicates that the target has produced some output.",
//     "properties": {
//       "event": {
//         "type": "string",
//         "enum": [ "output" ]
//       },
//       "body": {
//         "type": "object",
//         "properties": {
//           "category": {
//             "type": "string",
//             "description": "The output category. If not specified,
//                             'console' is assumed.",
//             "_enum": [ "console", "stdout", "stderr", "telemetry" ]
//           },
//           "output": {
//             "type": "string",
//             "description": "The output to report."
//           },
//           "variablesReference": {
//             "type": "number",
//             "description": "If an attribute 'variablesReference' exists
//                             and its value is > 0, the output contains
//                             objects which can be retrieved by passing
//                             variablesReference to the VariablesRequest."
//           },
//           "source": {
//             "$ref": "#/definitions/Source",
//             "description": "An optional source location where the output
//                             was produced."
//           },
//           "line": {
//             "type": "integer",
//             "description": "An optional source location line where the
//                             output was produced."
//           },
//           "column": {
//             "type": "integer",
//             "description": "An optional source location column where the
//                             output was produced."
//           },
//           "data": {
//             "type":["array","boolean","integer","null","number","object",
//                     "string"],
//             "description": "Optional data to report. For the 'telemetry'
//                             category the data will be sent to telemetry, for
//                             the other categories the data is shown in JSON
//                             format."
//           }
//         },
//         "required": ["output"]
//       }
//     },
//     "required": [ "event", "body" ]
//   }]
// }
void DAP::SendOutput(OutputType o, const llvm::StringRef output) {
  if (output.empty())
    return;

  const char *category = nullptr;
  switch (o) {
  case OutputType::Console:
    category = "console";
    break;
  case OutputType::Important:
    category = "important";
    break;
  case OutputType::Stdout:
    category = "stdout";
    break;
  case OutputType::Stderr:
    category = "stderr";
    break;
  case OutputType::Telemetry:
    category = "telemetry";
    break;
  }

  // Send each line of output as an individual event, including the newline if
  // present.
  ::size_t idx = 0;
  do {
    ::size_t end = output.find('\n', idx);
    if (end == llvm::StringRef::npos)
      end = output.size() - 1;
    llvm::json::Object event(CreateEventObject("output"));
    llvm::json::Object body;
    body.try_emplace("category", category);
    EmplaceSafeString(body, "output", output.slice(idx, end + 1).str());
    event.try_emplace("body", std::move(body));
    SendJSON(llvm::json::Value(std::move(event)));
    idx = end + 1;
  } while (idx < output.size());
}

// interface ProgressStartEvent extends Event {
//   event: 'progressStart';
//
//   body: {
//     /**
//      * An ID that must be used in subsequent 'progressUpdate' and
//      'progressEnd'
//      * events to make them refer to the same progress reporting.
//      * IDs must be unique within a debug session.
//      */
//     progressId: string;
//
//     /**
//      * Mandatory (short) title of the progress reporting. Shown in the UI to
//      * describe the long running operation.
//      */
//     title: string;
//
//     /**
//      * The request ID that this progress report is related to. If specified a
//      * debug adapter is expected to emit
//      * progress events for the long running request until the request has
//      been
//      * either completed or cancelled.
//      * If the request ID is omitted, the progress report is assumed to be
//      * related to some general activity of the debug adapter.
//      */
//     requestId?: number;
//
//     /**
//      * If true, the request that reports progress may be canceled with a
//      * 'cancel' request.
//      * So this property basically controls whether the client should use UX
//      that
//      * supports cancellation.
//      * Clients that don't support cancellation are allowed to ignore the
//      * setting.
//      */
//     cancellable?: boolean;
//
//     /**
//      * Optional, more detailed progress message.
//      */
//     message?: string;
//
//     /**
//      * Optional progress percentage to display (value range: 0 to 100). If
//      * omitted no percentage will be shown.
//      */
//     percentage?: number;
//   };
// }
//
// interface ProgressUpdateEvent extends Event {
//   event: 'progressUpdate';
//
//   body: {
//     /**
//      * The ID that was introduced in the initial 'progressStart' event.
//      */
//     progressId: string;
//
//     /**
//      * Optional, more detailed progress message. If omitted, the previous
//      * message (if any) is used.
//      */
//     message?: string;
//
//     /**
//      * Optional progress percentage to display (value range: 0 to 100). If
//      * omitted no percentage will be shown.
//      */
//     percentage?: number;
//   };
// }
//
// interface ProgressEndEvent extends Event {
//   event: 'progressEnd';
//
//   body: {
//     /**
//      * The ID that was introduced in the initial 'ProgressStartEvent'.
//      */
//     progressId: string;
//
//     /**
//      * Optional, more detailed progress message. If omitted, the previous
//      * message (if any) is used.
//      */
//     message?: string;
//   };
// }

void DAP::SendProgressEvent(uint64_t progress_id, const char *message,
                            uint64_t completed, uint64_t total) {
  progress_event_reporter.Push(progress_id, message, completed, total);
}

void __attribute__((format(printf, 3, 4)))
DAP::SendFormattedOutput(OutputType o, const char *format, ...) {
  char buffer[1024];
  va_list args;
  va_start(args, format);
  int actual_length = vsnprintf(buffer, sizeof(buffer), format, args);
  va_end(args);
  SendOutput(
      o, llvm::StringRef(buffer, std::min<int>(actual_length, sizeof(buffer))));
}

int32_t DAP::CreateSourceReference(lldb::addr_t address) {
  std::lock_guard<std::mutex> guard(m_source_references_mutex);
  auto iter = llvm::find(m_source_references, address);
  if (iter != m_source_references.end())
    return std::distance(m_source_references.begin(), iter) + 1;

  m_source_references.emplace_back(address);
  return static_cast<int32_t>(m_source_references.size());
}

std::optional<lldb::addr_t> DAP::GetSourceReferenceAddress(int32_t reference) {
  std::lock_guard<std::mutex> guard(m_source_references_mutex);
  if (reference <= LLDB_DAP_INVALID_SRC_REF)
    return std::nullopt;

  if (static_cast<size_t>(reference) > m_source_references.size())
    return std::nullopt;

  return m_source_references[reference - 1];
}

ExceptionBreakpoint *DAP::GetExceptionBPFromStopReason(lldb::SBThread &thread) {
  const auto num = thread.GetStopReasonDataCount();
  // Check to see if have hit an exception breakpoint and change the
  // reason to "exception", but only do so if all breakpoints that were
  // hit are exception breakpoints.
  ExceptionBreakpoint *exc_bp = nullptr;
  for (size_t i = 0; i < num; i += 2) {
    // thread.GetStopReasonDataAtIndex(i) will return the bp ID and
    // thread.GetStopReasonDataAtIndex(i+1) will return the location
    // within that breakpoint. We only care about the bp ID so we can
    // see if this is an exception breakpoint that is getting hit.
    lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
    exc_bp = GetExceptionBreakpoint(bp_id);
    // If any breakpoint is not an exception breakpoint, then stop and
    // report this as a normal breakpoint
    if (exc_bp == nullptr)
      return nullptr;
  }
  return exc_bp;
}

lldb::SBThread DAP::GetLLDBThread(lldb::tid_t tid) {
  return target.GetProcess().GetThreadByID(tid);
}

lldb::SBThread DAP::GetLLDBThread(const llvm::json::Object &arguments) {
  auto tid = GetInteger<int64_t>(arguments, "threadId")
                 .value_or(LLDB_INVALID_THREAD_ID);
  return target.GetProcess().GetThreadByID(tid);
}

lldb::SBFrame DAP::GetLLDBFrame(uint64_t frame_id) {
  if (frame_id == LLDB_DAP_INVALID_FRAME_ID)
    return lldb::SBFrame();

  lldb::SBProcess process = target.GetProcess();
  // Upper 32 bits is the thread index ID
  lldb::SBThread thread =
      process.GetThreadByIndexID(GetLLDBThreadIndexID(frame_id));
  // Lower 32 bits is the frame index
  return thread.GetFrameAtIndex(GetLLDBFrameID(frame_id));
}

lldb::SBFrame DAP::GetLLDBFrame(const llvm::json::Object &arguments) {
  const auto frame_id = GetInteger<uint64_t>(arguments, "frameId")
                            .value_or(LLDB_DAP_INVALID_FRAME_ID);
  return GetLLDBFrame(frame_id);
}

ReplMode DAP::DetectReplMode(lldb::SBFrame &frame, std::string &expression,
                             bool partial_expression) {
  // Check for the escape hatch prefix.
  if (llvm::StringRef expr_ref = expression;
      expr_ref.consume_front(configuration.commandEscapePrefix)) {
    expression = expr_ref;
    return ReplMode::Command;
  }

  if (repl_mode != ReplMode::Auto)
    return repl_mode;
  // To determine if the expression is a command or not, check if the first
  // term is a variable or command. If it's a variable in scope we will prefer
  // that behavior and give a warning to the user if they meant to invoke the
  // operation as a command.
  //
  // Example use case:
  //   int p and expression "p + 1" > variable
  //   int i and expression "i" > variable
  //   int var and expression "va" > command
  const auto [first_tok, remaining] = llvm::getToken(expression);

  // If the first token is not fully finished yet, we can't
  // determine whether this will be a variable or a lldb command.
  if (partial_expression && remaining.empty())
    return ReplMode::Auto;

  std::string first = first_tok.str();
  const char *first_cstr = first.c_str();
  lldb::SBCommandInterpreter interpreter = debugger.GetCommandInterpreter();
  const bool is_command = interpreter.CommandExists(first_cstr) ||
                          interpreter.UserCommandExists(first_cstr) ||
                          interpreter.AliasExists(first_cstr);
  const bool is_variable = frame.FindVariable(first_cstr).IsValid();

  // If we have both a variable and command, warn the user about the conflict.
  if (!partial_expression && is_command && is_variable) {
    const std::string warning_msg =
        llvm::formatv("warning: Expression '{}' is both an LLDB command and "
                      "variable. It will be evaluated as "
                      "a variable. To evaluate the expression as an LLDB "
                      "command, use '{}' as a prefix.\n",
                      first, configuration.commandEscapePrefix);
    SendOutput(OutputType::Console, warning_msg);
  }

  // Variables take preference to commands in auto, since commands can always
  // be called using the command_escape_prefix
  if (is_variable)
    return ReplMode::Variable;

  return is_command ? ReplMode::Command : ReplMode::Variable;
}

std::optional<protocol::Source> DAP::ResolveSource(const lldb::SBFrame &frame) {
  if (!frame.IsValid())
    return std::nullopt;

  const lldb::SBLineEntry frame_line_entry = frame.GetLineEntry();
  if (DisplayAssemblySource(debugger, frame_line_entry)) {
    const lldb::SBAddress frame_pc = frame.GetPCAddress();
    return ResolveAssemblySource(frame_pc);
  }

  return CreateSource(frame_line_entry.GetFileSpec());
}

std::optional<protocol::Source> DAP::ResolveSource(lldb::SBAddress address) {
  lldb::SBLineEntry line_entry = GetLineEntryForAddress(target, address);
  if (DisplayAssemblySource(debugger, line_entry))
    return ResolveAssemblySource(address);

  if (!line_entry.IsValid())
    return std::nullopt;

  return CreateSource(line_entry.GetFileSpec());
}

std::optional<protocol::Source>
DAP::ResolveAssemblySource(lldb::SBAddress address) {
  lldb::SBSymbol symbol = address.GetSymbol();
  lldb::addr_t load_addr = LLDB_INVALID_ADDRESS;
  std::string name;
  if (symbol.IsValid()) {
    load_addr = symbol.GetStartAddress().GetLoadAddress(target);
    name = symbol.GetName();
  } else {
    load_addr = address.GetLoadAddress(target);
    name = GetLoadAddressString(load_addr);
  }

  if (load_addr == LLDB_INVALID_ADDRESS)
    return std::nullopt;

  protocol::Source source;
  source.sourceReference = CreateSourceReference(load_addr);
  lldb::SBModule module = address.GetModule();
  if (module.IsValid()) {
    lldb::SBFileSpec file_spec = module.GetFileSpec();
    if (file_spec.IsValid()) {
      std::string path = GetSBFileSpecPath(file_spec);
      if (!path.empty())
        source.path = path + '`' + name;
    }
  }

  source.name = std::move(name);

  // Mark the source as deemphasized since users will only be able to view
  // assembly for these frames.
  source.presentationHint =
      protocol::Source::eSourcePresentationHintDeemphasize;

  return source;
}

bool DAP::RunLLDBCommands(llvm::StringRef prefix,
                          llvm::ArrayRef<std::string> commands) {
  bool required_command_failed = false;
  std::string output = ::RunLLDBCommands(
      debugger, prefix, commands, required_command_failed,
      /*parse_command_directives*/ true, /*echo_commands*/ true);
  SendOutput(OutputType::Console, output);
  return !required_command_failed;
}

static llvm::Error createRunLLDBCommandsErrorMessage(llvm::StringRef category) {
  return llvm::createStringError(
      llvm::inconvertibleErrorCode(),
      llvm::formatv(
          "Failed to run {0} commands. See the Debug Console for more details.",
          category)
          .str()
          .c_str());
}

llvm::Error
DAP::RunAttachCommands(llvm::ArrayRef<std::string> attach_commands) {
  if (!RunLLDBCommands("Running attachCommands:", attach_commands))
    return createRunLLDBCommandsErrorMessage("attach");
  return llvm::Error::success();
}

llvm::Error
DAP::RunLaunchCommands(llvm::ArrayRef<std::string> launch_commands) {
  if (!RunLLDBCommands("Running launchCommands:", launch_commands))
    return createRunLLDBCommandsErrorMessage("launch");
  return llvm::Error::success();
}

llvm::Error DAP::RunInitCommands() {
  if (!RunLLDBCommands("Running initCommands:", configuration.initCommands))
    return createRunLLDBCommandsErrorMessage("initCommands");
  return llvm::Error::success();
}

llvm::Error DAP::RunPreInitCommands() {
  if (!RunLLDBCommands("Running preInitCommands:",
                       configuration.preInitCommands))
    return createRunLLDBCommandsErrorMessage("preInitCommands");
  return llvm::Error::success();
}

llvm::Error DAP::RunPreRunCommands() {
  if (!RunLLDBCommands("Running preRunCommands:", configuration.preRunCommands))
    return createRunLLDBCommandsErrorMessage("preRunCommands");
  return llvm::Error::success();
}

void DAP::RunPostRunCommands() {
  RunLLDBCommands("Running postRunCommands:", configuration.postRunCommands);
}
void DAP::RunStopCommands() {
  RunLLDBCommands("Running stopCommands:", configuration.stopCommands);
}

void DAP::RunExitCommands() {
  RunLLDBCommands("Running exitCommands:", configuration.exitCommands);
}

void DAP::RunTerminateCommands() {
  RunLLDBCommands("Running terminateCommands:",
                  configuration.terminateCommands);
}

lldb::SBTarget DAP::CreateTarget(lldb::SBError &error) {
  // the given program as an argument. Executable file can be a source of target
  // architecture and platform, if they differ from the host. Setting exe path
  // in launch info is useless because Target.Launch() will not change
  // architecture and platform, therefore they should be known at the target
  // creation. We also use target triple and platform from the launch
  // configuration, if given, since in some cases ELF file doesn't contain
  // enough information to determine correct arch and platform (or ELF can be
  // omitted at all), so it is good to leave the user an opportunity to specify
  // those. Any of those three can be left empty.
  auto target = this->debugger.CreateTarget(
      /*filename=*/configuration.program.data(),
      /*target_triple=*/configuration.targetTriple.data(),
      /*platform_name=*/configuration.platformName.data(),
      /*add_dependent_modules=*/true, // Add dependent modules.
      error);

  return target;
}

void DAP::SetTarget(const lldb::SBTarget target) {
  this->target = target;

  if (target.IsValid()) {
    // Configure breakpoint event listeners for the target.
    lldb::SBListener listener = this->debugger.GetListener();
    listener.StartListeningForEvents(
        this->target.GetBroadcaster(),
        lldb::SBTarget::eBroadcastBitBreakpointChanged |
            lldb::SBTarget::eBroadcastBitModulesLoaded |
            lldb::SBTarget::eBroadcastBitModulesUnloaded |
            lldb::SBTarget::eBroadcastBitSymbolsLoaded |
            lldb::SBTarget::eBroadcastBitSymbolsChanged |
            lldb::SBTarget::eBroadcastBitNewTargetCreated);
    listener.StartListeningForEvents(this->broadcaster,
                                     eBroadcastBitStopEventThread);
  }
}

bool DAP::HandleObject(const Message &M) {
  TelemetryDispatcher dispatcher(&debugger);
  dispatcher.Set("client_name", m_client_name.str());
  if (const auto *req = std::get_if<Request>(&M)) {
    {
      std::lock_guard<std::mutex> guard(m_active_request_mutex);
      m_active_request = req;

      // Clear the interrupt request prior to invoking a handler.
      if (debugger.InterruptRequested())
        debugger.CancelInterruptRequest();
    }

    llvm::scope_exit cleanup([&]() {
      std::scoped_lock<std::mutex> active_request_lock(m_active_request_mutex);
      m_active_request = nullptr;
    });

    auto handler_pos = request_handlers.find(req->command);
    dispatcher.Set("client_data",
                   llvm::Twine("request_command:", req->command).str());
    if (handler_pos != request_handlers.end()) {
      handler_pos->second->Run(*req);
    } else {
      UnknownRequestHandler handler(*this);
      handler.BaseRequestHandler::Run(*req);
    }
    return true; // Success
  }

  if (const auto *resp = std::get_if<Response>(&M)) {
    std::unique_ptr<ResponseHandler> response_handler;
    {
      std::lock_guard<std::mutex> guard(call_mutex);
      auto inflight = inflight_reverse_requests.find(resp->request_seq);
      if (inflight != inflight_reverse_requests.end()) {
        response_handler = std::move(inflight->second);
        inflight_reverse_requests.erase(inflight);
      }
    }

    if (!response_handler)
      response_handler =
          std::make_unique<UnknownResponseHandler>("", resp->request_seq);

    // Result should be given, use null if not.
    if (resp->success) {
      (*response_handler)(resp->body);
      dispatcher.Set("client_data",
                     llvm::Twine("response_command:", resp->command).str());
    } else {
      llvm::StringRef message = "Unknown error, response failed";
      if (resp->message) {
        message =
            std::visit(llvm::makeVisitor(
                           [](const std::string &message) -> llvm::StringRef {
                             return message;
                           },
                           [](const protocol::ResponseMessage &message)
                               -> llvm::StringRef {
                             switch (message) {
                             case protocol::eResponseMessageCancelled:
                               return "cancelled";
                             case protocol::eResponseMessageNotStopped:
                               return "notStopped";
                             }
                             llvm_unreachable("unknown response message kind.");
                           }),
                       *resp->message);
      }
      dispatcher.Set("error", message.str());

      (*response_handler)(llvm::createStringError(
          std::error_code(-1, std::generic_category()), message));
    }

    return true;
  }

  dispatcher.Set("error", "Unsupported protocol message");
  DAP_LOG(log, "Unsupported protocol message");

  return false;
}

void DAP::SendTerminatedEvent() {
  // Prevent races if the process exits while we're being asked to disconnect.
  llvm::call_once(terminated_event_flag, [&] {
    RunTerminateCommands();
    // Send a "terminated" event
    llvm::json::Object event(CreateTerminatedEventObject(target));
    SendJSON(llvm::json::Value(std::move(event)));
  });
}

llvm::Error DAP::Disconnect() { return Disconnect(!is_attach); }

llvm::Error DAP::Disconnect(bool terminateDebuggee) {
  lldb::SBError error;
  lldb::SBProcess process = target.GetProcess();
  auto state = process.GetState();
  switch (state) {
  case lldb::eStateInvalid:
  case lldb::eStateUnloaded:
  case lldb::eStateDetached:
  case lldb::eStateExited:
    break;
  case lldb::eStateConnected:
  case lldb::eStateAttaching:
  case lldb::eStateLaunching:
  case lldb::eStateStepping:
  case lldb::eStateCrashed:
  case lldb::eStateSuspended:
  case lldb::eStateStopped:
  case lldb::eStateRunning: {
    ScopeSyncMode scope_sync_mode(debugger);
    error = terminateDebuggee ? process.Kill() : process.Detach();
    break;
  }
  }

  SendTerminatedEvent();
  TerminateLoop();
  return ToError(error);
}

bool DAP::IsCancelled(const protocol::Request &req) {
  std::lock_guard<std::mutex> guard(m_cancelled_requests_mutex);
  return m_cancelled_requests.contains(req.seq);
}

void DAP::ClearCancelRequest(const CancelArguments &args) {
  std::lock_guard<std::mutex> guard(m_cancelled_requests_mutex);
  if (args.requestId)
    m_cancelled_requests.erase(*args.requestId);
}

template <typename T>
static std::optional<T> getArgumentsIfRequest(const Request &req,
                                              llvm::StringLiteral command) {
  if (req.command != command)
    return std::nullopt;

  T args;
  llvm::json::Path::Root root;
  if (!fromJSON(req.arguments, args, root))
    return std::nullopt;

  return args;
}

void DAP::Received(const protocol::Event &event) {
  // no-op, no supported events from the client to the server as of DAP v1.68.
}

void DAP::Received(const protocol::Request &request) {
  if (request.command == "disconnect")
    m_disconnecting = true;

  const std::optional<CancelArguments> cancel_args =
      getArgumentsIfRequest<CancelArguments>(request, "cancel");
  if (cancel_args) {
    {
      std::lock_guard<std::mutex> guard(m_cancelled_requests_mutex);
      if (cancel_args->requestId)
        m_cancelled_requests.insert(*cancel_args->requestId);
    }

    // If a cancel is requested for the active request, make a best
    // effort attempt to interrupt.
    std::lock_guard<std::mutex> guard(m_active_request_mutex);
    if (m_active_request && cancel_args->requestId == m_active_request->seq) {
      DAP_LOG(log, "interrupting inflight request (command={0} seq={1})",
              m_active_request->command, m_active_request->seq);
      debugger.RequestInterrupt();
    }
  }

  std::lock_guard<std::mutex> guard(m_queue_mutex);
  DAP_LOG(log, "queued (command={0} seq={1})", request.command, request.seq);
  m_queue.push_back(request);
  m_queue_cv.notify_one();
}

void DAP::Received(const protocol::Response &response) {
  std::lock_guard<std::mutex> guard(m_queue_mutex);
  DAP_LOG(log, "queued (command={0} seq={1})", response.command,
          response.request_seq);
  m_queue.push_back(response);
  m_queue_cv.notify_one();
}

void DAP::OnError(llvm::Error error) {
  DAP_LOG_ERROR(log, std::move(error), "transport error: {0}");
  TerminateLoop(/*failed=*/true);
}

void DAP::OnClosed() {
  DAP_LOG(log, "transport closed");
  TerminateLoop();
}

void DAP::TerminateLoop(bool failed) {
  std::lock_guard<std::mutex> guard(m_queue_mutex);
  if (m_disconnecting)
    return; // Already disconnecting.

  m_error_occurred = failed;
  m_disconnecting = true;
  m_loop.AddPendingCallback(
      [](MainLoopBase &loop) { loop.RequestTermination(); });
}

void DAP::TransportHandler() {
  llvm::scope_exit scope_guard([this] {
    std::lock_guard<std::mutex> guard(m_queue_mutex);
    // Ensure we're marked as disconnecting when the reader exits.
    m_disconnecting = true;
    m_queue_cv.notify_all();
  });

  auto handle = transport.RegisterMessageHandler(m_loop, *this);
  if (!handle) {
    DAP_LOG_ERROR(log, handle.takeError(),
                  "registering message handler failed: {0}");
    std::lock_guard<std::mutex> guard(m_queue_mutex);
    m_error_occurred = true;
    return;
  }

  if (Status status = m_loop.Run(); status.Fail()) {
    DAP_LOG_ERROR(log, status.takeError(), "MainLoop run failed: {0}");
    std::lock_guard<std::mutex> guard(m_queue_mutex);
    m_error_occurred = true;
    return;
  }
}

llvm::Error DAP::Loop() {
  {
    // Reset disconnect flag once we start the loop.
    std::lock_guard<std::mutex> guard(m_queue_mutex);
    m_disconnecting = false;
  }

  auto thread = std::thread(std::bind(&DAP::TransportHandler, this));

  llvm::scope_exit cleanup([this]() {
    // FIXME: Merge these into the MainLoop handler.
    out.Stop();
    err.Stop();
    StopEventHandlers();

    // Destroy the debugger when the session ends. This will trigger the
    // debugger's destroy callbacks for earlier logging and clean-ups, rather
    // than waiting for the termination of the lldb-dap process.
    lldb::SBDebugger::Destroy(debugger);
  });

  while (true) {
    std::unique_lock<std::mutex> lock(m_queue_mutex);
    m_queue_cv.wait(lock, [&] { return m_disconnecting || !m_queue.empty(); });

    if (m_disconnecting && m_queue.empty())
      break;

    Message next = m_queue.front();
    m_queue.pop_front();

    // Unlock while we're processing the event.
    lock.unlock();

    if (!HandleObject(next))
      return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                     "unhandled packet");
  }

  // Don't wait to join the mainloop thread if our callback wasn't added
  // successfully, or we'll wait forever.
  if (m_loop.AddPendingCallback(
          [](MainLoopBase &loop) { loop.RequestTermination(); }))
    thread.join();

  if (m_error_occurred)
    return llvm::createStringError(llvm::inconvertibleErrorCode(),
                                   "DAP Loop terminated due to an internal "
                                   "error, see DAP Logs for more information.");
  return llvm::Error::success();
}

lldb::SBError DAP::WaitForProcessToStop(std::chrono::seconds seconds) {
  lldb::SBError error;
  lldb::SBProcess process = target.GetProcess();
  if (!process.IsValid()) {
    error.SetErrorString("invalid process");
    return error;
  }
  auto timeout_time =
      std::chrono::steady_clock::now() + std::chrono::seconds(seconds);
  while (std::chrono::steady_clock::now() < timeout_time) {
    const auto state = process.GetState();
    switch (state) {
    case lldb::eStateUnloaded:
    case lldb::eStateAttaching:
    case lldb::eStateConnected:
    case lldb::eStateInvalid:
    case lldb::eStateLaunching:
    case lldb::eStateRunning:
    case lldb::eStateStepping:
    case lldb::eStateSuspended:
      break;
    case lldb::eStateDetached:
      error.SetErrorString("process detached during launch or attach");
      return error;
    case lldb::eStateExited:
      error.SetErrorString("process exited during launch or attach");
      return error;
    case lldb::eStateCrashed:
    case lldb::eStateStopped:
      return lldb::SBError(); // Success!
    }
    std::this_thread::sleep_for(std::chrono::microseconds(250));
  }
  error.SetErrorString(
      llvm::formatv("process failed to stop within {0}", seconds)
          .str()
          .c_str());
  return error;
}

void DAP::ConfigureSourceMaps() {
  if (configuration.sourceMap.empty() && configuration.sourcePath.empty())
    return;

  std::string sourceMapCommand;
  llvm::raw_string_ostream strm(sourceMapCommand);
  strm << "settings set target.source-map ";

  if (!configuration.sourceMap.empty()) {
    for (const auto &kv : configuration.sourceMap) {
      strm << "\"" << kv.first << "\" \"" << kv.second << "\" ";
    }
  } else if (!configuration.sourcePath.empty()) {
    strm << "\".\" \"" << configuration.sourcePath << "\"";
  }

  RunLLDBCommands("Setting source map:", {sourceMapCommand});
}

void DAP::SetConfiguration(const protocol::Configuration &config,
                           bool is_attach) {
  configuration = config;
  stop_at_entry = config.stopOnEntry;
  this->is_attach = is_attach;

  if (configuration.customFrameFormat)
    SetFrameFormat(*configuration.customFrameFormat);
  if (configuration.customThreadFormat)
    SetThreadFormat(*configuration.customThreadFormat);
}

void DAP::SetFrameFormat(llvm::StringRef format) {
  lldb::SBError error;
  frame_format = lldb::SBFormat(format.str().c_str(), error);
  if (error.Fail()) {
    SendOutput(OutputType::Console,
               llvm::formatv(
                   "The provided frame format '{0}' couldn't be parsed: {1}\n",
                   format, error.GetCString())
                   .str());
  }
}

void DAP::SetThreadFormat(llvm::StringRef format) {
  lldb::SBError error;
  thread_format = lldb::SBFormat(format.str().c_str(), error);
  if (error.Fail()) {
    SendOutput(OutputType::Console,
               llvm::formatv(
                   "The provided thread format '{0}' couldn't be parsed: {1}\n",
                   format, error.GetCString())
                   .str());
  }
}

InstructionBreakpoint *
DAP::GetInstructionBreakpoint(const lldb::break_id_t bp_id) {
  for (auto &bp : instruction_breakpoints) {
    if (bp.second.GetID() == bp_id)
      return &bp.second;
  }
  return nullptr;
}

InstructionBreakpoint *
DAP::GetInstructionBPFromStopReason(lldb::SBThread &thread) {
  const auto num = thread.GetStopReasonDataCount();
  InstructionBreakpoint *inst_bp = nullptr;
  for (size_t i = 0; i < num; i += 2) {
    // thread.GetStopReasonDataAtIndex(i) will return the bp ID and
    // thread.GetStopReasonDataAtIndex(i+1) will return the location
    // within that breakpoint. We only care about the bp ID so we can
    // see if this is an instruction breakpoint that is getting hit.
    lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
    inst_bp = GetInstructionBreakpoint(bp_id);
    // If any breakpoint is not an instruction breakpoint, then stop and
    // report this as a normal breakpoint
    if (inst_bp == nullptr)
      return nullptr;
  }
  return inst_bp;
}

protocol::Capabilities DAP::GetCapabilities() {
  protocol::Capabilities capabilities;

  // Supported capabilities that are not specific to a single request.
  capabilities.supportedFeatures = {
      protocol::eAdapterFeatureLogPoints,
      protocol::eAdapterFeatureSteppingGranularity,
      protocol::eAdapterFeatureValueFormattingOptions,
  };

  // Capabilities associated with specific requests.
  for (auto &kv : request_handlers) {
    llvm::SmallDenseSet<AdapterFeature, 1> features =
        kv.second->GetSupportedFeatures();
    capabilities.supportedFeatures.insert(features.begin(), features.end());
  }

  // Available filters or options for the setExceptionBreakpoints request.
  PopulateExceptionBreakpoints();
  std::vector<protocol::ExceptionBreakpointsFilter> filters;
  for (const auto &exc_bp : exception_breakpoints)
    filters.emplace_back(CreateExceptionBreakpointFilter(exc_bp));
  capabilities.exceptionBreakpointFilters = std::move(filters);

  // FIXME: This should be registered based on the supported languages?
  std::vector<std::string> completion_characters;
  completion_characters.emplace_back(".");
  // FIXME: I wonder if we should remove this key... its very aggressive
  // triggering and accepting completions.
  completion_characters.emplace_back(" ");
  completion_characters.emplace_back("\t");
  capabilities.completionTriggerCharacters = std::move(completion_characters);

  // Put in non-DAP specification lldb specific information.
  capabilities.lldbExtVersion = debugger.GetVersionString();

  return capabilities;
}

protocol::Capabilities DAP::GetCustomCapabilities() {
  protocol::Capabilities capabilities;

  // Add all custom capabilities here.
  const llvm::DenseSet<AdapterFeature> all_custom_features = {
      protocol::eAdapterFeatureSupportsModuleSymbolsRequest,
  };

  for (auto &kv : request_handlers) {
    llvm::SmallDenseSet<AdapterFeature, 1> features =
        kv.second->GetSupportedFeatures();

    for (auto &feature : features) {
      if (all_custom_features.contains(feature))
        capabilities.supportedFeatures.insert(feature);
    }
  }

  return capabilities;
}

void DAP::StartEventThread() {
  // Get event thread for this debugger (creates it if it doesn't exist).
  event_thread_sp = DAPSessionManager::GetInstance().GetEventThreadForDebugger(
      debugger, this);
}

void DAP::StartProgressEventThread() {
  progress_event_thread = std::thread(&DAP::ProgressEventThread, this);
}

void DAP::StartEventThreads() {
  if (clientFeatures.contains(eClientFeatureProgressReporting))
    StartProgressEventThread();

  StartEventThread();
}

llvm::Error DAP::InitializeDebugger(const DAPSession &session) {
  // Find the existing debugger by ID
  lldb::SBDebugger found_debugger =
      lldb::SBDebugger::FindDebuggerWithID(session.debuggerId);
  if (!found_debugger.IsValid()) {
    return llvm::createStringError(
        "Unable to find existing debugger for debugger ID");
  }

  // Find the target within the debugger by its globally unique ID
  lldb::SBTarget target =
      found_debugger.FindTargetByGloballyUniqueID(session.targetId);
  if (!target.IsValid()) {
    return llvm::createStringError(
        "Unable to find existing target for target ID");
  }

  // Set the target and debugger for this DAP session.
  debugger = found_debugger;
  SetTarget(target);
  StartEventThreads();
  return llvm::Error::success();
}

llvm::Error DAP::InitializeDebugger() {
  debugger = lldb::SBDebugger::Create(/*argument_name=*/false);

  // Configure input/output/error file descriptors.
  debugger.SetInputFile(in);
  target = debugger.GetDummyTarget();

  llvm::Expected<int> out_fd = out.GetWriteFileDescriptor();
  if (!out_fd)
    return out_fd.takeError();
  debugger.SetOutputFile(lldb::SBFile(*out_fd, "w", false));

  llvm::Expected<int> err_fd = err.GetWriteFileDescriptor();
  if (!err_fd)
    return err_fd.takeError();
  debugger.SetErrorFile(lldb::SBFile(*err_fd, "w", false));

  // The sourceInitFile option is not part of the DAP specification. It is an
  // extension used by the test suite to prevent sourcing `.lldbinit` and
  // changing its behavior. The CLI flag --no-lldbinit takes precedence over
  // the DAP parameter.
  bool should_source_init_files = !no_lldbinit && sourceInitFile;
  if (should_source_init_files) {
    debugger.SkipLLDBInitFiles(false);
    debugger.SkipAppInitFiles(false);
    lldb::SBCommandReturnObject init;
    auto interp = debugger.GetCommandInterpreter();
    interp.SourceInitFileInGlobalDirectory(init);
    interp.SourceInitFileInHomeDirectory(init);
  }

  // Run initialization commands.
  if (llvm::Error err = RunPreInitCommands())
    return err;

  auto cmd = debugger.GetCommandInterpreter().AddMultiwordCommand(
      "lldb-dap", "Commands for managing lldb-dap.");

  if (clientFeatures.contains(eClientFeatureStartDebuggingRequest)) {
    cmd.AddCommand(
        "start-debugging", new StartDebuggingCommand(*this),
        "Sends a startDebugging request from the debug adapter to the client "
        "to start a child debug session of the same type as the caller.");
  }

  cmd.AddCommand(
      "repl-mode", new ReplModeCommand(*this),
      "Get or set the repl behavior of lldb-dap evaluation requests.");
  cmd.AddCommand("send-event", new SendEventCommand(*this),
                 "Sends an DAP event to the client.");

  StartEventThreads();
  return llvm::Error::success();
}

void DAP::ProgressEventThread() {
  lldb::SBListener listener("lldb-dap.progress.listener");
  debugger.GetBroadcaster().AddListener(
      listener, lldb::SBDebugger::eBroadcastBitProgress |
                    lldb::SBDebugger::eBroadcastBitExternalProgress);
  broadcaster.AddListener(listener, eBroadcastBitStopProgressThread);
  lldb::SBEvent event;
  bool done = false;
  while (!done) {
    if (listener.WaitForEvent(UINT32_MAX, event)) {
      const auto event_mask = event.GetType();
      if (event.BroadcasterMatchesRef(broadcaster)) {
        if (event_mask & eBroadcastBitStopProgressThread) {
          done = true;
        }
      } else {
        lldb::SBStructuredData data =
            lldb::SBDebugger::GetProgressDataFromEvent(event);

        const uint64_t progress_id =
            GetUintFromStructuredData(data, "progress_id");
        const uint64_t completed = GetUintFromStructuredData(data, "completed");
        const uint64_t total = GetUintFromStructuredData(data, "total");
        const std::string details =
            GetStringFromStructuredData(data, "details");

        if (completed == 0) {
          if (total == UINT64_MAX) {
            // This progress is non deterministic and won't get updated until it
            // is completed. Send the "message" which will be the combined title
            // and detail. The only other progress event for thus
            // non-deterministic progress will be the completed event So there
            // will be no need to update the detail.
            const std::string message =
                GetStringFromStructuredData(data, "message");
            SendProgressEvent(progress_id, message.c_str(), completed, total);
          } else {
            // This progress is deterministic and will receive updates,
            // on the progress creation event VSCode will save the message in
            // the create packet and use that as the title, so we send just the
            // title in the progressCreate packet followed immediately by a
            // detail packet, if there is any detail.
            const std::string title =
                GetStringFromStructuredData(data, "title");
            SendProgressEvent(progress_id, title.c_str(), completed, total);
            if (!details.empty())
              SendProgressEvent(progress_id, details.c_str(), completed, total);
          }
        } else {
          // This progress event is either the end of the progress dialog, or an
          // update with possible detail. The "detail" string we send to VS Code
          // will be appended to the progress dialog's initial text from when it
          // was created.
          SendProgressEvent(progress_id, details.c_str(), completed, total);
        }
      }
    }
  }
}

std::vector<protocol::Breakpoint> DAP::SetSourceBreakpoints(
    const protocol::Source &source,
    const std::optional<std::vector<protocol::SourceBreakpoint>> &breakpoints) {
  std::vector<protocol::Breakpoint> response_breakpoints;
  if (source.sourceReference) {
    // Breakpoint set by assembly source.
    auto &existing_breakpoints =
        m_source_assembly_breakpoints[*source.sourceReference];
    response_breakpoints =
        SetSourceBreakpoints(source, breakpoints, existing_breakpoints);
  } else {
    // Breakpoint set by a regular source file.
    const auto path = source.path.value_or("");
    auto &existing_breakpoints = m_source_breakpoints[path];
    response_breakpoints =
        SetSourceBreakpoints(source, breakpoints, existing_breakpoints);
  }

  return response_breakpoints;
}

std::vector<protocol::Breakpoint> DAP::SetSourceBreakpoints(
    const protocol::Source &source,
    const std::optional<std::vector<protocol::SourceBreakpoint>> &breakpoints,
    SourceBreakpointMap &existing_breakpoints) {
  std::vector<protocol::Breakpoint> response_breakpoints;

  SourceBreakpointMap request_breakpoints;
  if (breakpoints) {
    for (const auto &bp : *breakpoints) {
      SourceBreakpoint src_bp(*this, bp);
      std::pair<uint32_t, uint32_t> bp_pos(src_bp.GetLine(),
                                           src_bp.GetColumn());
      request_breakpoints.try_emplace(bp_pos, src_bp);

      const auto [iv, inserted] =
          existing_breakpoints.try_emplace(bp_pos, src_bp);
      // We check if this breakpoint already exists to update it.
      if (inserted) {
        if (llvm::Error error = iv->second.SetBreakpoint(source)) {
          protocol::Breakpoint invalid_breakpoint;
          invalid_breakpoint.message = llvm::toString(std::move(error));
          invalid_breakpoint.verified = false;
          response_breakpoints.push_back(std::move(invalid_breakpoint));
          existing_breakpoints.erase(iv);
          continue;
        }
      } else {
        iv->second.UpdateBreakpoint(src_bp);
      }

      protocol::Breakpoint response_breakpoint =
          iv->second.ToProtocolBreakpoint();

      if (!response_breakpoint.source)
        response_breakpoint.source = source;
      if (!response_breakpoint.line &&
          src_bp.GetLine() != LLDB_INVALID_LINE_NUMBER)
        response_breakpoint.line = src_bp.GetLine();
      if (!response_breakpoint.column &&
          src_bp.GetColumn() != LLDB_INVALID_COLUMN_NUMBER)
        response_breakpoint.column = src_bp.GetColumn();
      response_breakpoints.push_back(std::move(response_breakpoint));
    }
  }

  // Delete any breakpoints in this source file that aren't in the
  // request_bps set. There is no call to remove breakpoints other than
  // calling this function with a smaller or empty "breakpoints" list.
  for (auto it = existing_breakpoints.begin();
       it != existing_breakpoints.end();) {
    auto request_pos = request_breakpoints.find(it->first);
    if (request_pos == request_breakpoints.end()) {
      // This breakpoint no longer exists in this source file, delete it
      target.BreakpointDelete(it->second.GetID());
      it = existing_breakpoints.erase(it);
    } else {
      ++it;
    }
  }

  return response_breakpoints;
}

void DAP::RegisterRequests() {
  RegisterRequest<AttachRequestHandler>();
  RegisterRequest<BreakpointLocationsRequestHandler>();
  RegisterRequest<CancelRequestHandler>();
  RegisterRequest<CompletionsRequestHandler>();
  RegisterRequest<ConfigurationDoneRequestHandler>();
  RegisterRequest<ContinueRequestHandler>();
  RegisterRequest<DataBreakpointInfoRequestHandler>();
  RegisterRequest<DisassembleRequestHandler>();
  RegisterRequest<DisconnectRequestHandler>();
  RegisterRequest<EvaluateRequestHandler>();
  RegisterRequest<ExceptionInfoRequestHandler>();
  RegisterRequest<InitializeRequestHandler>();
  RegisterRequest<LaunchRequestHandler>();
  RegisterRequest<LocationsRequestHandler>();
  RegisterRequest<NextRequestHandler>();
  RegisterRequest<PauseRequestHandler>();
  RegisterRequest<ReadMemoryRequestHandler>();
  RegisterRequest<RestartRequestHandler>();
  RegisterRequest<ScopesRequestHandler>();
  RegisterRequest<SetBreakpointsRequestHandler>();
  RegisterRequest<SetDataBreakpointsRequestHandler>();
  RegisterRequest<SetExceptionBreakpointsRequestHandler>();
  RegisterRequest<SetFunctionBreakpointsRequestHandler>();
  RegisterRequest<SetInstructionBreakpointsRequestHandler>();
  RegisterRequest<SetVariableRequestHandler>();
  RegisterRequest<SourceRequestHandler>();
  RegisterRequest<StackTraceRequestHandler>();
  RegisterRequest<StepInRequestHandler>();
  RegisterRequest<StepInTargetsRequestHandler>();
  RegisterRequest<StepOutRequestHandler>();
  RegisterRequest<ThreadsRequestHandler>();
  RegisterRequest<VariablesRequestHandler>();
  RegisterRequest<WriteMemoryRequestHandler>();

  // Custom requests
  RegisterRequest<CompileUnitsRequestHandler>();
  RegisterRequest<ModulesRequestHandler>();
  RegisterRequest<ModuleSymbolsRequestHandler>();

  // Testing requests
  RegisterRequest<TestGetTargetBreakpointsRequestHandler>();
}

} // namespace lldb_dap
