// Copyright (c) 2019, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

// TODO(bkonyi): autogenerate from service_extensions.md

import 'dart:collection';
import 'dart:typed_data';

import 'vm_service.dart' hide Error;

extension DartIOExtension on VmService {
  static bool _factoriesRegistered = false;
  static final Map<String, Version> _isolateVersion = {};

  Future<Version> _version(String isolateId) async {
    Version? version = _isolateVersion[isolateId];
    if (version == null) {
      version = await getDartIOVersion(isolateId);
      _isolateVersion[isolateId] = version;
    }
    return version;
  }

  /// The `getDartIOVersion` RPC returns the available version of the dart:io
  /// service protocol extensions.
  Future<Version> getDartIOVersion(String isolateId) =>
      _callHelper('ext.dart.io.getVersion', isolateId);

  /// Whether socket profiling is available for the given [isolateId].
  Future<bool> isSocketProfilingAvailable(String isolateId) async {
    final Isolate isolate = await getIsolate(isolateId);
    return (isolate.extensionRPCs ?? [])
        .contains('ext.dart.io.getSocketProfile');
  }

  /// The _socketProfilingEnabled_ RPC is used to enable/disable the socket profiler
  /// and query its current state. If `enabled` is provided, the profiler state will
  /// be updated to reflect the value of `enabled`.
  ///
  /// If the state of the socket profiler is changed, a `SocketProfilingStateChange`
  /// event will be sent on the `Extension` stream.
  Future<SocketProfilingState> socketProfilingEnabled(String isolateId,
      [bool? enabled]) async {
    assert(await isSocketProfilingAvailable(isolateId));
    return _callHelper('ext.dart.io.socketProfilingEnabled', isolateId, args: {
      if (enabled != null) 'enabled': enabled,
    });
  }

  /// Removes all statistics associated with prior and current sockets.
  Future<Success> clearSocketProfile(String isolateId) async {
    assert(await isSocketProfilingAvailable(isolateId));
    return _callHelper('ext.dart.io.clearSocketProfile', isolateId);
  }

  /// The `getSocketProfile` RPC is used to retrieve socket statistics collected
  /// by the socket profiler. Only samples collected after the initial
  /// [socketProfilingEnabled] call or the last call to [clearSocketProfile]
  /// will be reported.
  Future<SocketProfile> getSocketProfile(String isolateId) async {
    assert(await isSocketProfilingAvailable(isolateId));
    return _callHelper('ext.dart.io.getSocketProfile', isolateId);
  }

  /// Whether HTTP timeline logging is available for the given [isolateId].
  Future<bool> isHttpTimelineLoggingAvailable(String isolateId) async {
    final Isolate isolate = await getIsolate(isolateId);
    final rpcs = isolate.extensionRPCs ?? [];
    return rpcs.contains('ext.dart.io.httpEnableTimelineLogging');
  }

  /// The `httpEnableTimelineLogging` RPC is used to set and inspect the value of
  /// `HttpClient.enableTimelineLogging`, which determines if HTTP client requests
  /// should be logged to the timeline. If `enabled` is provided, the state of
  /// `HttpClient.enableTimelineLogging` will be updated to the value of `enabled`.
  ///
  /// If the value of `HttpClient.enableTimelineLogging` is changed, a
  /// `HttpTimelineLoggingStateChange` event will be sent on the `Extension` stream.
  Future<HttpTimelineLoggingState> httpEnableTimelineLogging(
    String isolateId, [
    bool? enabled,
  ]) async {
    assert(await isHttpTimelineLoggingAvailable(isolateId));
    final version = await _version(isolateId);
    // Parameter name changed in version 1.4.
    final enableKey =
        ((version.major! == 1 && version.minor! > 3) || version.major! >= 2)
            ? 'enabled'
            : 'enable';
    return _callHelper('ext.dart.io.httpEnableTimelineLogging', isolateId,
        args: {
          if (enabled != null) enableKey: enabled,
        });
  }

  /// Whether HTTP profiling is available for the given [isolateId].
  Future<bool> isHttpProfilingAvailable(String isolateId) async {
    final Isolate isolate = await getIsolate(isolateId);
    return (isolate.extensionRPCs ?? []).contains('ext.dart.io.getHttpProfile');
  }

  /// The `getHttpProfile` RPC is used to retrieve HTTP profiling information
  /// for requests made via `dart:io`'s `HttpClient`.
  ///
  /// The returned [HttpProfile] will only include requests issued after
  /// `httpTimelineLogging` has been enabled or after the last
  /// `clearHttpProfile` invocation.
  ///
  /// If `updatedSince` is provided, only requests started or updated since
  /// the specified time will be reported.
  Future<HttpProfile> getHttpProfile(
    String isolateId, {
    DateTime? updatedSince,
  }) async {
    assert(await isHttpProfilingAvailable(isolateId));
    return _callHelper('ext.dart.io.getHttpProfile', isolateId, args: {
      if (updatedSince != null)
        'updatedSince': updatedSince.microsecondsSinceEpoch,
    });
  }

  /// The `getHttpProfileRequest` RPC is used to retrieve an instance of
  /// [HttpProfileRequest], which includes request and response body data.
  Future<HttpProfileRequest> getHttpProfileRequest(
    String isolateId,
    String id,
  ) async {
    assert(await isHttpProfilingAvailable(isolateId));
    return _callHelper('ext.dart.io.getHttpProfileRequest', isolateId, args: {
      'id': id,
    });
  }

  /// The `clearHttpProfile` RPC is used to clear previously recorded HTTP
  /// requests from the HTTP profiler state. Requests still in-flight after
  /// clearing the profiler state will be ignored by the profiler.
  Future<Success> clearHttpProfile(String isolateId) async {
    assert(await isHttpProfilingAvailable(isolateId));
    return _callHelper(
      'ext.dart.io.clearHttpProfile',
      isolateId,
    );
  }

  /// The `getOpenFiles` RPC is used to retrieve the list of files currently
  /// opened files by `dart:io` from a given isolate.
  Future<OpenFileList> getOpenFiles(String isolateId) => _callHelper(
        'ext.dart.io.getOpenFiles',
        isolateId,
      );

  /// The `getOpenFileById` RPC is used to retrieve information about files
  /// currently opened by `dart:io` from a given isolate.
  Future<OpenFile> getOpenFileById(String isolateId, int id) => _callHelper(
        'ext.dart.io.getOpenFileById',
        isolateId,
        args: {
          'id': id,
        },
      );

  /// The `getSpawnedProcesses` RPC is used to retrieve the list of processed opened
  /// by `dart:io` from a given isolate
  Future<SpawnedProcessList> getSpawnedProcesses(String isolateId) =>
      _callHelper(
        'ext.dart.io.getSpawnedProcesses',
        isolateId,
      );

  /// The `getSpawnedProcessById` RPC is used to retrieve information about a process
  /// spawned by `dart:io` from a given isolate.
  Future<SpawnedProcess> getSpawnedProcessById(String isolateId, int id) =>
      _callHelper(
        'ext.dart.io.getSpawnedProcessById',
        isolateId,
        args: {
          'id': id,
        },
      );

  Future<T> _callHelper<T>(String method, String? isolateId,
      {Map args = const {}}) {
    if (!_factoriesRegistered) {
      _registerFactories();
    }
    return extensionCallHelper(
      this,
      method,
      {
        if (isolateId != null) 'isolateId': isolateId,
        ...args,
      },
    );
  }

  static void _registerFactories() {
    addTypeFactory('OpenFile', OpenFile.parse);
    addTypeFactory('OpenFileList', OpenFileList.parse);
    addTypeFactory('@OpenFile', OpenFileRef.parse);
    addTypeFactory('HttpTimelineLoggingState', HttpTimelineLoggingState.parse);
    addTypeFactory('SpawnedProcess', SpawnedProcess.parse);
    addTypeFactory('SpawnedProcessList', SpawnedProcessList.parse);
    addTypeFactory('@SpawnedProcess', SpawnedProcessRef.parse);
    addTypeFactory('SocketProfile', SocketProfile.parse);
    addTypeFactory('SocketStatistic', SocketStatistic.parse);
    addTypeFactory('SocketProfilingState', SocketProfilingState.parse);
    addTypeFactory('HttpProfile', HttpProfile.parse);
    addTypeFactory('HttpProfileRequest', HttpProfileRequest.parse);
    _factoriesRegistered = true;
  }
}

class SocketStatistic {
  static SocketStatistic? parse(Map<String, dynamic>? json) =>
      json == null ? null : SocketStatistic._fromJson(json);

  /// The unique ID associated with this socket.
  final String id;

  /// The time, in microseconds, that this socket was created.
  final int startTime;

  /// The time, in microseconds, that this socket was closed.
  @optional
  final int? endTime;

  /// The time, in microseconds, that this socket was last read from.
  @optional
  final int? lastReadTime;

  /// The time, in microseconds, that this socket was last written to.
  @optional
  final int? lastWriteTime;

  /// The address of the socket.
  final String address;

  /// The port of the socket.
  final int port;

  /// The type of socket. The value is either `tcp` or `udp`.
  final String socketType;

  /// The number of bytes read from this socket.
  final int readBytes;

  /// The number of bytes written to this socket.
  final int writeBytes;

  SocketStatistic._fromJson(Map<String, dynamic> json)
      : id = json['id'],
        startTime = json['startTime'],
        endTime = json['endTime'],
        lastReadTime = json['lastReadTime'],
        lastWriteTime = json['lastWriteTime'],
        address = json['address'],
        port = json['port'],
        socketType = json['socketType'],
        readBytes = json['readBytes'],
        writeBytes = json['writeBytes'];
}

/// A [SocketProfile] provides information about statistics of sockets.
class SocketProfile extends Response {
  static SocketProfile? parse(Map<String, dynamic>? json) =>
      json == null ? null : SocketProfile._fromJson(json);

  @override
  String get type => 'SocketProfile';

  /// List of socket statistics.
  late final List<SocketStatistic> sockets;

  SocketProfile({required this.sockets});

  SocketProfile._fromJson(Map<String, dynamic> json) {
    // TODO(bkonyi): make this part of the vm_service.dart library so we can
    // call super._fromJson.
    sockets = List<SocketStatistic>.from(
        createServiceObject(json['sockets'], const ['SocketStatistic'])
                as List? ??
            []);
  }
}

/// A [Response] containing the enabled state of a service extension.
abstract class _State extends Response {
  _State({required this.enabled}) : _type = 'State';

  // TODO(bkonyi): make this part of the vm_service.dart library so we can
  // call super._fromJson.
  _State._fromJson(Map<String, dynamic> json)
      : enabled = json['enabled'],
        _type = json['type'];

  @override
  String get type => _type;

  final bool enabled;
  final String _type;
}

/// An [HttpTimelineLoggingState] provides information about the current state
/// of HTTP request logging for a given isolate.
class HttpTimelineLoggingState extends _State {
  static HttpTimelineLoggingState? parse(Map<String, dynamic>? json) =>
      json == null ? null : HttpTimelineLoggingState._fromJson(json);

  @override
  String get type => 'HttpTimelineLoggingState';

  HttpTimelineLoggingState({required super.enabled});

  HttpTimelineLoggingState._fromJson(super.json) : super._fromJson();
}

/// A collection of HTTP request data collected by the profiler.
class HttpProfile extends Response {
  static HttpProfile? parse(Map<String, dynamic>? json) =>
      json == null ? null : HttpProfile._fromJson(json);

  HttpProfile._fromJson(Map<String, dynamic> json)
      : timestamp = DateTime.fromMicrosecondsSinceEpoch(json['timestamp']!),
        requests = json['requests']!
            .cast<Map<String, dynamic>>()
            .map((e) => HttpProfileRequest._fromJson(e))
            .cast<HttpProfileRequest>()
            .toList();

  HttpProfile({required this.requests, required this.timestamp});

  @override
  String get type => 'HttpProfile';

  @override
  String toString() => '[HttpProfile]';

  /// The time at which this HTTP profile was built.
  final DateTime timestamp;

  /// The set of recorded HTTP requests.
  final List<HttpProfileRequest> requests;
}

/// Profiling information for a single HTTP request.
class HttpProfileRequestRef {
  static HttpProfileRequestRef? parse(Map<String, dynamic>? json) =>
      json == null ? null : HttpProfileRequestRef._fromJson(json);

  HttpProfileRequestRef._fromJson(Map<String, dynamic> json)
      : isolateId = json['isolateId']!,
        id = json['id']!,
        method = json['method']!,
        uri = Uri.parse(json['uri']!),
        events = json['events']!
            .cast<Map<String, dynamic>>()
            .map((e) => HttpProfileRequestEvent._fromJson(e))
            .cast<HttpProfileRequestEvent>()
            .toList(),
        startTime = DateTime.fromMicrosecondsSinceEpoch(json['startTime']!),
        endTime = json['endTime'] == null
            ? null
            : DateTime.fromMicrosecondsSinceEpoch(json['endTime']),
        request = HttpProfileRequestData.parse(json['request']),
        response = HttpProfileResponseData.parse(json['response']);

  HttpProfileRequestRef({
    required this.isolateId,
    required this.id,
    required this.method,
    required this.uri,
    required this.events,
    required this.startTime,
    this.endTime,
    this.request,
    this.response,
  });

  // The ID of the isolate this request was issued from.
  final String isolateId;

  /// The ID associated with this request.
  ///
  /// If the ID does not start with the prefix 'from_package/', then there
  /// will be a corresponding timeline event with the same ID.
  final String id;

  /// The HTTP request method associated with this request.
  final String method;

  /// The URI to which this HTTP request was sent.
  final Uri uri;

  /// Events related to this HTTP request.
  final List<HttpProfileRequestEvent> events;

  /// The time at which this request was initiated.
  final DateTime startTime;

  /// The time at which this request was completed.
  ///
  /// Will be `null` if the request is still in progress.
  final DateTime? endTime;

  /// Returns `true` if the initial HTTP request has completed.
  bool get isRequestComplete => endTime != null;

  /// Returns `true` if the entirety of the response has been received.
  bool get isResponseComplete => response?.isComplete ?? false;

  /// Details about the request.
  ///
  /// Can be `null` if the request has not yet been completed.
  final HttpProfileRequestData? request;

  /// Details about the response.
  ///
  /// Can be `null` if the request has not yet been responded to.
  final HttpProfileResponseData? response;
}

/// Profiling information for a single HTTP request, including request and
/// response body data.
class HttpProfileRequest extends HttpProfileRequestRef {
  static HttpProfileRequest? parse(Map<String, dynamic>? json) =>
      json == null ? null : HttpProfileRequest._fromJson(json);

  HttpProfileRequest._fromJson(super.json)
      : requestBody =
            Uint8List.fromList(json['requestBody']?.cast<int>() ?? <int>[]),
        responseBody =
            Uint8List.fromList(json['responseBody']?.cast<int>() ?? <int>[]),
        super._fromJson();

  HttpProfileRequest({
    required super.id,
    required super.isolateId,
    required super.method,
    required super.uri,
    required super.events,
    required super.startTime,
    super.endTime,
    super.request,
    super.response,
    this.requestBody,
    this.responseBody,
  });

  /// The body sent as part of this request.
  ///
  /// Data written to a request body before encountering an error will be
  /// reported.
  final Uint8List? requestBody;

  /// The body received in response to the request.
  final Uint8List? responseBody;
}

/// Information sent as part of the initial HTTP request.
class HttpProfileRequestData {
  static HttpProfileRequestData? parse(Map<String, dynamic>? json) =>
      json == null ? null : HttpProfileRequestData._fromJson(json);

  HttpProfileRequestData._fromJson(Map<String, dynamic> json)
      : _headers = UnmodifiableMapView(json['headers'] ?? {}),
        _connectionInfo = UnmodifiableMapView(json['connectionInfo'] ?? {}),
        _contentLength = json['contentLength'],
        _cookies = UnmodifiableListView(json['cookies']?.cast<String>() ?? []),
        _followRedirects = json['followRedirects'] ?? false,
        _maxRedirects = json['maxRedirects'] ?? 0,
        _persistentConnection = json['persistentConnection'] ?? false,
        _proxyDetails = HttpProfileProxyData.parse(json['proxyDetails']),
        error = json['error'];

  HttpProfileRequestData.buildSuccessfulRequest({
    Map<String, dynamic>? headers,
    Map<String, dynamic>? connectionInfo,
    int? contentLength,
    required List<String> cookies,
    bool? followRedirects,
    int? maxRedirects,
    bool? persistentConnection,
    HttpProfileProxyData? proxyDetails,
  })  : _headers = headers,
        _connectionInfo = connectionInfo,
        _contentLength = contentLength,
        _cookies = cookies,
        _followRedirects = followRedirects,
        _maxRedirects = maxRedirects,
        _persistentConnection = persistentConnection,
        _proxyDetails = proxyDetails,
        error = null;

  HttpProfileRequestData.buildErrorRequest({
    required this.error,
  })  : _connectionInfo = null,
        _contentLength = null,
        _cookies = null,
        _followRedirects = null,
        _headers = null,
        _maxRedirects = null,
        _persistentConnection = null,
        _proxyDetails = null;

  /// Returns `true` if an error has occurred while issuing the request.
  ///
  /// If `true`, attempting to access some fields will throw an
  /// [HttpProfileRequestError].
  bool get hasError => error != null;

  /// Information about the client connection.
  ///
  /// Throws [HttpProfileRequestError] if `hasError` is `true`.
  Map<String, dynamic>? get connectionInfo => _returnIfNoError(_connectionInfo);
  final Map<String, dynamic>? _connectionInfo;

  /// The content length of the request, in bytes.
  ///
  /// Throws [HttpProfileRequestError] if `hasError` is `true`.
  int? get contentLength => _returnIfNoError(_contentLength);
  final int? _contentLength;

  /// Cookies presented to the server (in the 'cookie' header).
  ///
  /// Throws [HttpProfileRequestError] if `hasError` is `true`.
  List<String>? get cookies => _returnIfNoError(_cookies);
  final List<String>? _cookies;

  /// The error associated with the failed request.
  final String? error;

  /// Whether automatic redirect following was enabled for the request.
  ///
  /// Throws [HttpProfileRequestError] if `hasError` is `true`.
  bool? get followRedirects => _returnIfNoError(_followRedirects);
  final bool? _followRedirects;

  /// The client request headers.
  ///
  /// Throws [HttpProfileRequestError] if `hasError` is `true`.
  Map<String, dynamic>? get headers => _returnIfNoError(_headers);
  final Map<String, dynamic>? _headers;

  /// The maximum number of redirects allowed during the request.
  ///
  /// Throws [HttpProfileRequestError] if `hasError` is `true`.
  int? get maxRedirects => _returnIfNoError(_maxRedirects);
  final int? _maxRedirects;

  /// The requested persistent connection state.
  ///
  /// Throws [HttpProfileRequestError] if `hasError` is `true`.
  bool? get persistentConnection => _returnIfNoError(_persistentConnection);
  final bool? _persistentConnection;

  /// Proxy authentication details for this request.
  ///
  /// Throws [HttpProfileRequestError] if `hasError` is `true`.
  HttpProfileProxyData? get proxyDetails => _returnIfNoError(_proxyDetails);
  final HttpProfileProxyData? _proxyDetails;

  T? _returnIfNoError<T>(T? field) {
    if (hasError) {
      throw HttpProfileRequestError(error!);
    }
    return field;
  }
}

/// An [Error] thrown when attempting to inspect fields in a
/// [HttpProfileRequestData] instance when `hasError` is `true`.
class HttpProfileRequestError implements Error {
  HttpProfileRequestError(this.error);

  final String error;

  @override
  final StackTrace stackTrace = StackTrace.current;

  @override
  String toString() => 'HttpProfileRequestError: $error.';
}

/// Describes proxy authentication details associated with an
/// [HttpProfileRequest].
class HttpProfileProxyData {
  static HttpProfileProxyData? parse(Map<String, dynamic>? json) =>
      json == null ? null : HttpProfileProxyData._fromJson(json);

  HttpProfileProxyData._fromJson(Map<String, dynamic> json)
      : host = json['host'],
        username = json['username'],
        isDirect = json['isDirect'],
        port = json['port'];

  HttpProfileProxyData({
    this.host,
    this.username,
    this.isDirect,
    this.port,
  });

  /// The URI of the proxy server.
  final String? host;

  /// The username used to authenticate with the proxy server.
  final String? username;

  final bool? isDirect;

  /// The port the proxy server is listening on.
  final int? port;
}

/// Describes an event related to an HTTP request.
class HttpProfileRequestEvent {
  static HttpProfileRequestEvent? parse(Map<String, dynamic>? json) =>
      json == null ? null : HttpProfileRequestEvent._fromJson(json);

  HttpProfileRequestEvent._fromJson(Map<String, dynamic> json)
      : timestamp = DateTime.fromMicrosecondsSinceEpoch(json['timestamp']!),
        event = json['event']!,
        arguments = json['arguments'];

  HttpProfileRequestEvent({
    required this.event,
    required this.timestamp,
    this.arguments,
  });

  /// The title of the recorded event.
  final String event;

  /// The time at which the event occurred.
  final DateTime timestamp;

  final Map<String, dynamic>? arguments;
}

/// Information received in response to an initial HTTP request.
class HttpProfileResponseData {
  static HttpProfileResponseData? parse(Map<String, dynamic>? json) =>
      json == null ? null : HttpProfileResponseData._fromJson(json);

  HttpProfileResponseData._fromJson(Map<String, dynamic> json)
      : startTime = json['startTime'] == null
            ? null
            : DateTime.fromMicrosecondsSinceEpoch(json['startTime']),
        endTime = json['endTime'] == null
            ? null
            : DateTime.fromMicrosecondsSinceEpoch(json['endTime']),
        headers = json['headers'],
        connectionInfo = json['connectionInfo'],
        contentLength = json['contentLength'],
        compressionState = json['compressionState'],
        cookies = UnmodifiableListView(json['cookies']?.cast<String>() ?? []),
        error = json['error'],
        isRedirect = json['isRedirect'],
        persistentConnection = json['persistentConnection'],
        reasonPhrase = json['reasonPhrase'],
        redirects = UnmodifiableListView(
            json['redirects']!.cast<Map<String, dynamic>>()),
        statusCode = json['statusCode'];

  HttpProfileResponseData({
    this.startTime,
    this.endTime,
    this.headers,
    this.compressionState,
    this.connectionInfo,
    this.contentLength,
    this.cookies,
    this.isRedirect,
    this.persistentConnection,
    this.reasonPhrase,
    required this.redirects,
    this.statusCode,
    this.error,
  });

  bool get isComplete => endTime != null;
  bool get hasError => error != null;

  /// The series of redirects this connection has been through.
  ///
  /// The list will be empty if no redirects were followed. Redirects will be
  /// updated both in the case of an automatic and a manual redirect.
  final List<Map<String, dynamic>> redirects;

  /// Cookies set by the server (from the 'set-cookie' header).
  final List<String>? cookies;

  /// Information about the client connection.
  final Map<String, dynamic>? connectionInfo;

  /// The client response headers.
  final Map<String, dynamic>? headers;

  /// The compression state of the response.
  ///
  /// This specifies whether the response bytes were compressed when they were
  /// received across the wire and whether callers will receive compressed or
  /// uncompressed bytes when they listed to this response's byte stream.
  ///
  /// See [HttpClientResponseCompressionState](https://api.dart.dev/dart-io/HttpClientResponseCompressionState-class.html) for possible values.
  final String? compressionState;

  /// The reason phrase associated with the status code.
  final String? reasonPhrase;

  /// Whether the status code is one of the normal redirect codes.
  final bool? isRedirect;

  /// The persistent connection state returned by the server.
  final bool? persistentConnection;

  /// The content length of the response body, in bytes.
  ///
  /// Returns -1 if the size of the response body is not known in advance.
  final int? contentLength;

  /// The status code.
  final int? statusCode;

  /// The time at which the initial response was received.
  final DateTime? startTime;

  /// The time at which the response was completed.
  ///
  /// Will be `null` if response data is still being received.
  final DateTime? endTime;

  /// The error associated with the failed response.
  final String? error;
}

/// A [SocketProfilingState] provides information about the current state of
/// socket profiling for a given isolate.
class SocketProfilingState extends _State {
  static SocketProfilingState? parse(Map<String, dynamic>? json) =>
      json == null ? null : SocketProfilingState._fromJson(json);

  SocketProfilingState({required super.enabled});

  SocketProfilingState._fromJson(super.json) : super._fromJson();
}

/// A [SpawnedProcessRef] contains identifying information about a spawned
/// process.
class SpawnedProcessRef {
  static SpawnedProcessRef? parse(Map<String, dynamic>? json) =>
      json == null ? null : SpawnedProcessRef._fromJson(json);

  SpawnedProcessRef({
    required this.id,
    required this.name,
  });

  SpawnedProcessRef._fromJson(Map<String, dynamic> json)
      :
        // TODO(bkonyi): make this part of the vm_service.dart library so we can
        // call super._fromJson.
        id = json['id'],
        name = json['name'];

  static const String type = 'SpawnedProcessRef';

  /// The unique ID associated with this process.
  final int id;

  /// The name of the executable.
  final String name;
}

/// A [SpawnedProcess] contains startup information of a spawned process.
class SpawnedProcess extends Response implements SpawnedProcessRef {
  static SpawnedProcess? parse(Map<String, dynamic>? json) =>
      json == null ? null : SpawnedProcess._fromJson(json);

  SpawnedProcess({
    required this.id,
    required this.name,
    required this.pid,
    required this.startedAt,
    required List<String> arguments,
    required this.workingDirectory,
  }) : _arguments = arguments;

  SpawnedProcess._fromJson(Map<String, dynamic> json)
      :
        // TODO(bkonyi): make this part of the vm_service.dart library so we can
        // call super._fromJson.
        id = json['id'],
        name = json['name'],
        pid = json['pid'],
        startedAt = json['startedAt'],
        _arguments = List<String>.from(
            createServiceObject(json['arguments'], const ['String']) as List),
        workingDirectory = json['workingDirectory'];

  @override
  String get type => 'SpawnedProcess';

  /// The unique ID associated with this process.
  @override
  final int id;

  /// The name of the executable.
  @override
  final String name;

  /// The process ID associated with the process.
  final int pid;

  /// The time the process was started in milliseconds since epoch.
  final int startedAt;

  /// The list of arguments provided to the process at launch.
  List<String> get arguments => UnmodifiableListView(_arguments);
  final List<String> _arguments;

  /// The working directory of the process at launch.
  final String workingDirectory;
}

class SpawnedProcessList extends Response {
  static SpawnedProcessList? parse(Map<String, dynamic>? json) =>
      json == null ? null : SpawnedProcessList._fromJson(json);

  SpawnedProcessList({required List<SpawnedProcessRef> processes})
      : _processes = processes;

  SpawnedProcessList._fromJson(Map<String, dynamic> json)
      :
        // TODO(bkonyi): make this part of the vm_service.dart library so we can
        // call super._fromJson.
        _processes = List<SpawnedProcessRef>.from(
            createServiceObject(json['processes'], const ['SpawnedProcessRef'])
                as List);

  @override
  String get type => 'SpawnedProcessList';

  /// A list of processes spawned through dart:io on a given isolate.
  List<SpawnedProcessRef> get processes => UnmodifiableListView(_processes);
  final List<SpawnedProcessRef> _processes;
}

/// An [OpenFileRef] contains identifying information about a currently opened
/// file.
class OpenFileRef {
  static OpenFileRef? parse(Map<String, dynamic>? json) =>
      json == null ? null : OpenFileRef._fromJson(json);

  OpenFileRef({
    required this.id,
    required this.name,
  });

  OpenFileRef._fromJson(Map<String, dynamic> json)
      :
        // TODO(bkonyi): make this part of the vm_service.dart library so we can
        // call super._fromJson.
        id = json['id'],
        name = json['name'];

  static const String type = 'OpenFileRef';

  /// The unique ID associated with this file.
  final int id;

  /// The path of the file.
  final String name;
}

/// Contains information about reads and writes to a currently opened file.
class OpenFile extends Response implements OpenFileRef {
  static OpenFile? parse(Map<String, dynamic>? json) =>
      json == null ? null : OpenFile._fromJson(json);

  OpenFile({
    required this.id,
    required this.name,
    required this.readBytes,
    required this.writeBytes,
    required this.readCount,
    required this.writeCount,
    required this.lastReadTime,
    required this.lastWriteTime,
  });

  OpenFile._fromJson(Map<String, dynamic> json)
      :
        // TODO(bkonyi): make this part of the vm_service.dart library so we can
        // call super._fromJson.
        id = json['id'],
        name = json['name'],
        readBytes = json['readBytes'],
        writeBytes = json['writeBytes'],
        readCount = json['readCount'],
        writeCount = json['writeCount'],
        lastReadTime =
            DateTime.fromMillisecondsSinceEpoch(json['lastReadTime']),
        lastWriteTime =
            DateTime.fromMillisecondsSinceEpoch(json['lastWriteTime']);

  @override
  String get type => 'OpenFile';

  /// The unique ID associated with this file.
  @override
  final int id;

  /// The path of the file.
  @override
  final String name;

  /// The total number of bytes read from this file.
  final int readBytes;

  /// The total number of bytes written to this file.
  final int writeBytes;

  /// The number of reads made from this file.
  final int readCount;

  /// The number of writes made to this file.
  final int writeCount;

  /// The time at which this file was last read by this process.
  final DateTime lastReadTime;

  /// The time at which this file was last written to by this process.
  final DateTime lastWriteTime;
}

class OpenFileList extends Response {
  static OpenFileList? parse(Map<String, dynamic>? json) =>
      json == null ? null : OpenFileList._fromJson(json);

  OpenFileList({required List<OpenFileRef> files}) : _files = files;

  OpenFileList._fromJson(Map<String, dynamic> json)
      :
        // TODO(bkonyi): make this part of the vm_service.dart library so we can
        // call super._fromJson.
        _files = List<OpenFileRef>.from(
            createServiceObject(json['files'], const ['OpenFileRef']) as List);

  @override
  String get type => 'OpenFileList';

  /// A list of all files opened through dart:io on a given isolate.
  List<OpenFileRef> get files => UnmodifiableListView(_files);
  final List<OpenFileRef> _files;
}
