class DEBUGGER__::ThreadClient

def process_dap args

def process_dap args
  # pp tc: self, args: args
  type = args.shift
  req = args.shift
  case type
  when :backtrace
    start_frame = req.dig('arguments', 'startFrame') || 0
    levels = req.dig('arguments', 'levels') || 1_000
    frames = []
    @target_frames.each_with_index do |frame, i|
      next if i < start_frame
      path = frame.realpath || frame.path
      next if skip_path?(path) && !SESSION.stop_stepping?(path, frame.location.lineno)
      break if (levels -= 1) < 0
      source_name = path ? File.basename(path) : frame.location.to_s
      if (path && File.exist?(path)) && (local_path = UI_DAP.remote_to_local_path(path))
        # ok
      else
        ref = frame.file_lines
      end
      frames << {
        id: i, # id is refilled by SESSION
        name: frame.name,
        line: frame.location.lineno,
        column: 1,
        source: {
          name: source_name,
          path: (local_path || path),
          sourceReference: ref,
        },
      }
    end
    event! :protocol_result, :backtrace, req, {
      stackFrames: frames,
      totalFrames: @target_frames.size,
    }
  when :scopes
    fid = args.shift
    frame = get_frame(fid)
    lnum =
      if frame.binding
        frame.binding.local_variables.size
      elsif vars = frame.local_variables
        vars.size
      else
        0
      end
    event! :protocol_result, :scopes, req, scopes: [{
      name: 'Local variables',
      presentationHint: 'locals',
      # variablesReference: N, # filled by SESSION
      namedVariables: lnum,
      indexedVariables: 0,
      expensive: false,
    }, {
      name: 'Global variables',
      presentationHint: 'globals',
      variablesReference: 1, # GLOBAL
      namedVariables: safe_global_variables.size,
      indexedVariables: 0,
      expensive: false,
    }]
  when :scope
    fid = args.shift
    frame = get_frame(fid)
    vars = collect_locals(frame).map do |var, val|
      variable(var, val)
    end
    event! :protocol_result, :scope, req, variables: vars, tid: self.id
  when :variable
    vid = args.shift
    obj = @var_map[vid]
    if obj
      case req.dig('arguments', 'filter')
      when 'indexed'
        start = req.dig('arguments', 'start') || 0
        count = req.dig('arguments', 'count') || obj.size
        vars = (start ... (start + count)).map{|i|
          variable(i.to_s, obj[i])
        }
      else
        vars = []
        case obj
        when Hash
          vars = obj.map{|k, v|
            variable(value_inspect(k), v,)
          }
        when Struct
          vars = obj.members.map{|m|
            variable(m, obj[m])
          }
        when String
          vars = [
            variable('#length', obj.length),
            variable('#encoding', obj.encoding),
          ]
          printed_str = value_inspect(obj)
          vars << variable('#dump', NaiveString.new(obj)) if printed_str.end_with?('...')
        when Class, Module
          vars << variable('%ancestors', obj.ancestors[1..])
        when Range
          vars = [
            variable('#begin', obj.begin),
            variable('#end', obj.end),
          ]
        end
        unless NaiveString === obj
          vars += M_INSTANCE_VARIABLES.bind_call(obj).sort.map{|iv|
            variable(iv, M_INSTANCE_VARIABLE_GET.bind_call(obj, iv))
          }
          vars.unshift variable('#class', M_CLASS.bind_call(obj))
        end
      end
    end
    event! :protocol_result, :variable, req, variables: (vars || []), tid: self.id
  when :evaluate
    fid, expr, context = args
    frame = get_frame(fid)
    message = nil
    if frame && (b = frame.eval_binding)
      special_local_variables frame do |name, var|
        b.local_variable_set(name, var) if /\%/ !~ name
      end
      case context
      when 'repl', 'watch'
        result = dap_eval b, expr, context, prompt: '(DEBUG CONSOLE)'
      when 'hover'
        case expr
        when /\A\@\S/
          begin
            result = M_INSTANCE_VARIABLE_GET.bind_call(b.receiver, expr)
          rescue NameError
            message = "Error: Not defined instance variable: #{expr.inspect}"
          end
        when /\A\$\S/
          safe_global_variables.each{|gvar|
            if gvar.to_s == expr
              result = eval(gvar.to_s)
              break false
            end
          } and (message = "Error: Not defined global variable: #{expr.inspect}")
        when /\Aself$/
          result = b.receiver
        when /(\A((::[A-Z]|[A-Z])\w*)+)/
          unless result = search_const(b, $1)
            message = "Error: Not defined constants: #{expr.inspect}"
          end
        else
          begin
            result = b.local_variable_get(expr)
          rescue NameError
            # try to check method
            if M_RESPOND_TO_P.bind_call(b.receiver, expr, include_all: true)
              result = M_METHOD.bind_call(b.receiver, expr)
            else
              message = "Error: Can not evaluate: #{expr.inspect}"
            end
          end
        end
      else
        message = "Error: unknown context: #{context}"
      end
    else
      result = 'Error: Can not evaluate on this frame'
    end
    event! :protocol_result, :evaluate, req, message: message, tid: self.id, **evaluate_result(result)
  when :completions
    fid, text = args
    frame = get_frame(fid)
    if (b = frame&.binding) && word = text&.split(/[\s\{]/)&.last
      words = IRB::InputCompletor::retrieve_completion_data(word, bind: b).compact
    end
    event! :protocol_result, :completions, req, targets: (words || []).map{|phrase|
      detail = nil
      if /\b([_a-zA-Z]\w*[!\?]?)\z/ =~ phrase
        w = $1
      else
        w = phrase
      end
      begin
        v = b.local_variable_get(w)
        detail ="(variable: #{value_inspect(v)})"
      rescue NameError
      end
      {
        label: phrase,
        text: w,
        detail: detail,
      }
    }
  else
    if respond_to? mid = "custom_dap_request_#{type}"
      __send__ mid, req
    else
      raise "Unknown request: #{args.inspect}"
    end
  end
end