private void ResetOnDisconnect()
        {
            // consider resetting this.events filter enabled state to defaults from constructor

            _options = new LaunchAttach();
            _breakpoints.Clear();
            _output.ValueCodes.Clear();
            ContinueAllThreads();
        }
        private async Task <Message> DispatchAsync(Message message, CancellationToken cancellationToken)
        {
            if (message is Request request)
            {
                if (message is Request <Initialize> initialize)
                {
                    var body     = MakeCapabilities();
                    var response = Response.From(NextSeq, initialize, body);
                    await SendAsync(response, cancellationToken).ConfigureAwait(false);

                    return(Event.From(NextSeq, "initialized", new { }));
                }

                if (message is Request <Launch> launch)
                {
                    _options = launch.Arguments;
                    return(Response.From(NextSeq, launch, new { }));
                }

                if (message is Request <Attach> attach)
                {
                    _options = attach.Arguments;
                    return(Response.From(NextSeq, attach, new { }));
                }

                if (message is Request <SetBreakpoints> setBreakpoints)
                {
                    var arguments = setBreakpoints.Arguments;
                    var file      = Path.GetFileName(arguments.Source.Path);
                    await OutputAsync($"Set breakpoints for {file}", null, null, cancellationToken).ConfigureAwait(false);

                    var breakpoints = _breakpoints.SetBreakpoints(arguments.Source, arguments.Breakpoints);
                    foreach (var breakpoint in breakpoints)
                    {
                        if (breakpoint.Verified)
                        {
                            var item = _breakpoints.ItemFor(breakpoint);
                            await OutputAsync($"Set breakpoint at {_codeModel.NameFor(item)}", item, null, cancellationToken).ConfigureAwait(false);
                        }
                    }

                    return(Response.From(NextSeq, setBreakpoints, new { breakpoints }));
                }

                if (message is Request <SetFunctionBreakpoints> setFunctionBreakpoints)
                {
                    var arguments = setFunctionBreakpoints.Arguments;
                    await OutputAsync("Set function breakpoints.", null, null, cancellationToken).ConfigureAwait(false);

                    var breakpoints = _breakpoints.SetBreakpoints(arguments.Breakpoints);
                    foreach (var breakpoint in breakpoints)
                    {
                        if (breakpoint.Verified)
                        {
                            var item = _breakpoints.ItemFor(breakpoint);
                            await OutputAsync($"Set breakpoint at {_codeModel.NameFor(item)}", item, null, cancellationToken).ConfigureAwait(false);
                        }
                    }

                    return(Response.From(NextSeq, setFunctionBreakpoints, new { breakpoints }));
                }

                if (message is Request <SetExceptionBreakpoints> setExceptionBreakpoints)
                {
                    var arguments = setExceptionBreakpoints.Arguments;
                    _events.Reset(arguments.Filters);

                    return(Response.From(NextSeq, setExceptionBreakpoints, new { }));
                }

                if (message is Request <Threads> threads)
                {
                    var body = new
                    {
                        threads = _threads.Select(t => new
                        {
                            id   = t.Key,
                            name = t.Value.Name
                        }).ToArray()
                    };

                    return(Response.From(NextSeq, threads, body));
                }

                if (message is Request <StackTrace> stackTrace)
                {
                    var arguments = stackTrace.Arguments;
                    var thread    = _threads[arguments.ThreadId];

                    var frames      = thread.Frames;
                    var stackFrames = new List <StackFrame>();
                    foreach (var frame in frames)
                    {
                        var stackFrame = new StackFrame
                        {
                            Id   = EncodeFrame(thread, frame),
                            Name = frame.Name
                        };

                        var item = _codeModel.NameFor(frame.Item);
                        DebuggerSourceMap.Assign(stackFrame, item, frame.More);

                        if (_sourceMap.TryGetValue(frame.Item, out var range))
                        {
                            DebuggerSourceMap.Assign(stackFrame, range);
                        }

                        stackFrames.Add(stackFrame);
                    }

                    return(Response.From(NextSeq, stackTrace, new { stackFrames }));
                }

                if (message is Request <Scopes> scopes)
                {
                    var arguments = scopes.Arguments;
                    DecodeFrame(arguments.FrameId, out var thread, out var frame);
                    const bool expensive = false;

                    var body = new
                    {
                        scopes = new[]
                        {
                            new
                            {
                                expensive,
                                name = frame.Name,
                                variablesReference = EncodeValue(thread, frame.Data)
                            }
                        }
                    };

                    return(Response.From(NextSeq, scopes, body));
                }

                if (message is Request <Variables> vars)
                {
                    var arguments = vars.Arguments;
                    DecodeValue(arguments.VariablesReference, out var arena, out var context);

                    var names = _dataModel.Names(context);

                    var body = new
                    {
                        variables = (from name in names
                                     let value = _dataModel[context, name]
                                                 let variablesReference = EncodeValue(arena, value)
                                                                          select new
                        {
                            name = _dataModel.ToString(name),
                            value = _dataModel.ToString(value),
                            variablesReference
                        })
                                    .ToArray()
                    };

                    return(Response.From(NextSeq, vars, body));
                }

                if (message is Request <SetVariable> setVariable)
                {
                    var arguments = setVariable.Arguments;
                    DecodeValue(arguments.VariablesReference, out var arena, out var context);

                    var value = _dataModel[context, arguments.Name] = JToken.Parse(arguments.Value);

                    var body = new
                    {
                        value = _dataModel.ToString(value),
                        variablesReference = EncodeValue(arena, value)
                    };

                    return(Response.From(NextSeq, setVariable, body));
                }

                if (message is Request <Evaluate> evaluate)
                {
                    var arguments = evaluate.Arguments;
                    DecodeFrame(arguments.FrameId, out var thread, out var frame);
                    var expression = arguments.Expression.Trim('"');

                    try
                    {
                        var result = frame.Evaluate(expression);
                        var body   = new
                        {
                            result             = _dataModel.ToString(result),
                            variablesReference = EncodeValue(thread, result),
                        };

                        return(Response.From(NextSeq, evaluate, body));
                    }
#pragma warning disable CA1031 // Do not catch general exception types (catch any exception and return it)
                    catch (Exception ex)
#pragma warning restore CA1031 // Do not catch general exception types
                    {
                        return(Response.Fail(NextSeq, evaluate, ex.Message));
                    }
                }

                Response Post <TBody>(PerThread perThread, Phase phase, TBody body)
                {
                    // "constructing the response" and "posting to the thread" have side-effects
                    // for extra determinism, construct the response before signaling the thread
                    var response = Response.From(NextSeq, request, body);

                    var found = _threads.TryGetValue(perThread.ThreadId, out var thread);

                    if (found)
                    {
                        thread.Run.Post(phase);
                    }

                    return(response);
                }

                if (message is Request <Continue> cont)
                {
                    return(Post(cont.Arguments, Phase.Continue, new { allThreadsContinued = false }));
                }

                if (message is Request <Pause> pause)
                {
                    return(Post(pause.Arguments, Phase.Pause, new { }));
                }

                if (message is Request <Next> next)
                {
                    return(Post(next.Arguments, Phase.Next, new { }));
                }

                if (message is Request <Terminate> terminate)
                {
                    if (_terminate != null)
                    {
                        _terminate();
                    }

                    return(Response.From(NextSeq, terminate, new { }));
                }

                if (message is Request <Disconnect> disconnect)
                {
                    var arguments = disconnect.Arguments;
                    if (arguments.TerminateDebuggee && _terminate != null)
                    {
                        _terminate();
                    }
                    else
                    {
                        ResetOnDisconnect();
                    }

                    return(Response.From(NextSeq, disconnect, new { }));
                }

                return(Response.From(NextSeq, request, new { }));
            }

            if (message is Event @event)
            {
                throw new NotImplementedException();
            }

            throw new NotImplementedException();
        }