// ignore_for_file: unused_import, unnecessary_import

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:web_socket_channel/io.dart';
import '../app/app_metadata.dart';
import 'device_identity_store.dart';
import 'platform_environment.dart';
import 'runtime_models.dart';
import 'secure_config_store.dart';
import 'gateway_runtime_protocol.dart';
import 'gateway_runtime_events.dart';
import 'gateway_runtime_errors.dart';
import 'gateway_runtime_core.dart';

String formatGatewayConnectAuthSummary({
  required String mode,
  required List<String> fields,
  required List<String> sources,
}) {
  final resolvedFields = fields.isEmpty ? 'none' : fields.join(', ');
  final resolvedSources = sources.isEmpty ? 'none' : sources.join(' · ');
  return '$mode | fields: $resolvedFields | sources: $resolvedSources';
}

mixin GatewayRuntimeHelpersInternal on ChangeNotifier {
  Future<RpcResponseInternal> requestRawInternal(
    GatewayRuntime runtime,
    String method, {
    Map<String, dynamic>? params,
    Duration timeout = const Duration(seconds: 15),
  }) async {
    final channel = runtime.channelInternal;
    if (channel == null) {
      throw GatewayRuntimeException('gateway not connected', code: 'OFFLINE');
    }
    final id =
        '${DateTime.now().microsecondsSinceEpoch}-${runtime.requestCounterInternal++}';
    final completer = Completer<RpcResponseInternal>();
    runtime.pendingInternal[id] = completer;
    final frame = <String, dynamic>{
      'type': 'req',
      'id': id,
      'method': method,
      ...?params == null ? null : <String, dynamic>{'params': params},
    };
    channel.sink.add(jsonEncode(frame));
    try {
      return await completer.future.timeout(
        timeout,
        onTimeout: () => throw GatewayRuntimeException(
          '$method request timeout',
          code: 'RPC_TIMEOUT',
        ),
      );
    } finally {
      runtime.pendingInternal.remove(id);
    }
  }

  GatewayPendingDevice parsePendingDeviceInternal(Map<String, dynamic> map) {
    return GatewayPendingDevice(
      requestId: stringValue(map['requestId']) ?? randomIdInternal(),
      deviceId: stringValue(map['deviceId']) ?? 'unknown-device',
      displayName: stringValue(map['displayName']),
      role: stringValue(map['role']),
      scopes: stringList(map['scopes']),
      remoteIp: stringValue(map['remoteIp']),
      isRepair: boolValue(map['isRepair']) ?? false,
      requestedAtMs: intValue(map['ts']),
    );
  }

  GatewayPairedDevice parsePairedDeviceInternal(
    Map<String, dynamic> map, {
    String? currentDeviceId,
  }) {
    return GatewayPairedDevice(
      deviceId: stringValue(map['deviceId']) ?? 'unknown-device',
      displayName: stringValue(map['displayName']),
      roles: stringList(map['roles']),
      scopes: stringList(map['scopes']),
      remoteIp: stringValue(map['remoteIp']),
      tokens: asList(map['tokens'])
          .map((item) => parseTokenSummaryInternal(asMap(item)))
          .toList(growable: false),
      createdAtMs: intValue(map['createdAtMs']),
      approvedAtMs: intValue(map['approvedAtMs']),
      currentDevice:
          currentDeviceId != null &&
          currentDeviceId.isNotEmpty &&
          currentDeviceId == stringValue(map['deviceId']),
    );
  }

  GatewayDeviceTokenSummary parseTokenSummaryInternal(
    Map<String, dynamic> map,
  ) {
    return GatewayDeviceTokenSummary(
      role: stringValue(map['role']) ?? 'operator',
      scopes: stringList(map['scopes']),
      createdAtMs: intValue(map['createdAtMs']),
      rotatedAtMs: intValue(map['rotatedAtMs']),
      revokedAtMs: intValue(map['revokedAtMs']),
      lastUsedAtMs: intValue(map['lastUsedAtMs']),
    );
  }

  Future<Map<String, dynamic>> buildConnectParamsInternal(
    GatewayRuntime runtime, {
    required GatewayConnectionProfile profile,
    required LocalDeviceIdentity identity,
    required String nonce,
    required String authToken,
    required String authDeviceToken,
    required String authPassword,
  }) async {
    final clientId = resolveClientIdInternal();
    final clientMode = 'ui';
    final signedAtMs = DateTime.now().millisecondsSinceEpoch;
    final signaturePayload = runtime.identityStoreInternal
        .buildDeviceAuthPayloadV3(
          deviceId: identity.deviceId,
          clientId: clientId,
          clientMode: clientMode,
          role: 'operator',
          scopes: kDefaultOperatorConnectScopes,
          signedAtMs: signedAtMs,
          token: authToken,
          nonce: nonce,
          platform: runtime.deviceInfoInternal.platformLabel,
          deviceFamily: runtime.deviceInfoInternal.deviceFamily,
        );
    final signature = await runtime.identityStoreInternal.signPayload(
      identity: identity,
      payload: signaturePayload,
    );

    return <String, dynamic>{
      'minProtocol': kGatewayProtocolVersion,
      'maxProtocol': kGatewayProtocolVersion,
      'client': <String, dynamic>{
        'id': clientId,
        'displayName':
            '$kSystemAppName ${runtime.deviceInfoInternal.deviceFamily}',
        'version': runtime.packageInfoInternal.version,
        'platform': runtime.deviceInfoInternal.platformLabel,
        'deviceFamily': runtime.deviceInfoInternal.deviceFamily,
        'modelIdentifier': runtime.deviceInfoInternal.modelIdentifier,
        'mode': clientMode,
        'instanceId':
            '$clientId-${identity.deviceId.substring(0, min(8, identity.deviceId.length))}',
      },
      'caps': const <String>['tool-events'],
      'commands': const <String>[],
      'permissions': const <String, bool>{},
      'role': 'operator',
      'scopes': kDefaultOperatorConnectScopes,
      if (authToken.isNotEmpty ||
          authDeviceToken.isNotEmpty ||
          authPassword.isNotEmpty)
        'auth': <String, dynamic>{
          if (authToken.isNotEmpty) 'token': authToken,
          if (authDeviceToken.isNotEmpty) 'deviceToken': authDeviceToken,
          if (authPassword.isNotEmpty) 'password': authPassword,
        },
      'locale': Platform.localeName,
      'userAgent': '$kSystemAppName/${runtime.packageInfoInternal.version}',
      'device': <String, dynamic>{
        'id': identity.deviceId,
        'publicKey': identity.publicKeyBase64Url,
        'signature': signature,
        'signedAt': signedAtMs,
        'nonce': nonce,
      },
    };
  }

  (String, int, bool)? resolveEndpointInternal(
    GatewayConnectionProfile profile,
  ) {
    final payload = decodeGatewaySetupCode(profile.setupCode);
    if (profile.useSetupCode && payload != null) {
      return (payload.host, payload.port, payload.tls);
    }
    final host = profile.host.trim();
    if (host.isEmpty) {
      return null;
    }
    final normalized = parseGatewayEndpoint(
      host.contains('://')
          ? host
          : composeManualUrlInternal(host, profile.port, profile.tls),
    );
    return normalized ?? (host, profile.port, profile.tls);
  }

  void handleIncomingInternal(
    GatewayRuntime runtime,
    dynamic raw,
    Completer<String> challenge,
  ) {
    final text = raw is String ? raw : utf8.decode(raw as List<int>);
    final decoded = jsonDecode(text) as Map<String, dynamic>;

    // Handle Events / Notifications
    final event = stringValue(decoded['event']) ?? stringValue(decoded['method']);
    final type = stringValue(decoded['type']);

    if (event != null || type == 'event') {
      final resolvedEvent = event ?? '';
      final payload = decoded['payload'] ?? decoded['params'];
      if (resolvedEvent == 'connect.challenge') {
        final nonce = stringValue(asMap(payload)['nonce']);
        if (nonce != null && !challenge.isCompleted) {
          challenge.complete(nonce);
        }
        appendLogInternal(runtime, 'debug', 'connect', 'challenge received');
        return;
      }
      if (resolvedEvent == 'health') {
        runtime.snapshotInternal = runtime.snapshotInternal.copyWith(
          healthPayload: asMap(payload),
        );
        appendLogInternal(runtime, 'debug', 'health', 'push health update');
        runtime.notifyListeners();
      } else if (resolvedEvent == 'device.pair.requested' ||
          resolvedEvent == 'device.pair.resolved') {
        final eventPayload = asMap(payload);
        appendLogInternal(
          runtime,
          'info',
          'pairing',
          '$resolvedEvent | request: ${stringValue(eventPayload['requestId']) ?? 'unknown'} | device: ${stringValue(eventPayload['deviceId']) ?? 'unknown'}',
        );
      } else if (resolvedEvent == 'seqGap') {
        appendLogInternal(runtime, 'warn', 'sync', 'sequence gap detected');
      }
      runtime.eventsInternal.add(
        GatewayPushEvent(
          event: resolvedEvent,
          payload: payload,
          sequence: intValue(decoded['seq']) ?? 0,
        ),
      );
      return;
    }

    // Handle Responses
    final id = stringValue(decoded['id']);
    if (id == null) {
      return;
    }
    final completer = runtime.pendingInternal.remove(id);
    if (completer == null || completer.isCompleted) {
      return;
    }

    final hasResult = decoded.containsKey('result');
    final hasError = decoded.containsKey('error');
    final ok = boolValue(decoded['ok']) ?? (hasResult && !hasError);
    final payload = decoded['payload'] ?? decoded['result'];
    final error = asMap(decoded['error']);

    if (!ok) {
      appendLogInternal(
        runtime,
        'error',
        'rpc',
        'request failed | code: ${stringValue(error['code']) ?? 'unknown'} | detail: ${stringValue(asMap(error['details'])['code']) ?? 'none'} | message: ${stringValue(error['message']) ?? 'gateway request failed'}',
      );
      if (!shouldAutoReconnectForCodesInternal(
        stringValue(error['code']),
        stringValue(asMap(error['details'])['code']),
      )) {
        runtime.suppressReconnectInternal = true;
      }
      completer.completeError(
        GatewayRuntimeException(
          stringValue(error['message']) ?? 'gateway request failed',
          code: stringValue(error['code']),
          details: error['details'],
        ),
      );
      return;
    }
    completer.complete(
      RpcResponseInternal(ok: ok, payload: payload, error: error),
    );
  }

  void handleSocketFailureInternal(GatewayRuntime runtime, String message) {
    failPendingInternal(
      runtime,
      GatewayRuntimeException(message, code: 'SOCKET_FAILURE'),
    );
    if (runtime.manualDisconnectInternal || runtime.suppressReconnectInternal) {
      appendLogInternal(
        runtime,
        'warn',
        'socket',
        'failure ignored for reconnect | manual: ${runtime.manualDisconnectInternal} | suppressed: ${runtime.suppressReconnectInternal} | message: $message',
      );
      return;
    }
    appendLogInternal(runtime, 'error', 'socket', 'failure | $message');
    runtime.snapshotInternal = runtime.snapshotInternal.copyWith(
      status: RuntimeConnectionStatus.error,
      statusText: 'Gateway error',
      lastError: message,
      lastErrorCode: 'SOCKET_FAILURE',
      lastErrorDetailCode: null,
    );
    runtime.notifyListeners();
    scheduleReconnectInternal(runtime);
  }

  void handleSocketClosedInternal(GatewayRuntime runtime) {
    failPendingInternal(
      runtime,
      GatewayRuntimeException('socket closed', code: 'SOCKET_CLOSED'),
    );
    if (runtime.manualDisconnectInternal || runtime.suppressReconnectInternal) {
      appendLogInternal(
        runtime,
        'warn',
        'socket',
        'closed without reconnect | manual: ${runtime.manualDisconnectInternal} | suppressed: ${runtime.suppressReconnectInternal}',
      );
      return;
    }
    appendLogInternal(runtime, 'warn', 'socket', 'closed by gateway');
    runtime.snapshotInternal = runtime.snapshotInternal.copyWith(
      status: RuntimeConnectionStatus.error,
      statusText: 'Disconnected',
      lastError: 'Gateway connection closed',
      lastErrorCode: 'SOCKET_CLOSED',
      lastErrorDetailCode: null,
    );
    runtime.notifyListeners();
    scheduleReconnectInternal(runtime);
  }

  String cronScheduleLabelInternal(Map<String, dynamic> schedule) {
    final kind = stringValue(schedule['kind']) ?? '';
    return switch (kind) {
      'at' => stringValue(schedule['at']) ?? 'at',
      'every' => '${intValue(schedule['everyMs']) ?? 0}ms',
      'cron' => stringValue(schedule['expr']) ?? 'cron',
      _ => 'unknown',
    };
  }

  void scheduleReconnectInternal(GatewayRuntime runtime) {
    final profile = runtime.desiredProfileInternal;
    if (runtime.manualDisconnectInternal ||
        runtime.suppressReconnectInternal ||
        profile == null) {
      return;
    }
    runtime.reconnectTimerInternal?.cancel();
    runtime.reconnectTimerInternal = Timer(const Duration(seconds: 2), () {
      appendLogInternal(
        runtime,
        'info',
        'socket',
        'reconnect firing | host: ${profile.host.trim().isEmpty ? 'setup-code' : profile.host.trim()} | port: ${profile.port}',
      );
      unawaited(runtime.connectProfile(profile));
    });
  }

  bool shouldAutoReconnectInternal(GatewayRuntimeException? error) {
    return shouldAutoReconnectForCodesInternal(error?.code, error?.detailCode);
  }

  bool shouldAutoReconnectForCodesInternal(String? code, String? detailCode) {
    final resolvedCode = code?.trim().toUpperCase();
    final resolvedDetailCode = detailCode?.trim().toUpperCase();
    const nonRetryableCodes = <String>{
      'INVALID_REQUEST',
      'UNAUTHORIZED',
      'NOT_PAIRED',
      'AUTH_REQUIRED',
    };
    const nonRetryableDetailCodes = <String>{
      'AUTH_REQUIRED',
      'AUTH_UNAUTHORIZED',
      'AUTH_TOKEN_MISSING',
      'AUTH_TOKEN_MISMATCH',
      'AUTH_PASSWORD_MISSING',
      'AUTH_PASSWORD_MISMATCH',
      'AUTH_DEVICE_TOKEN_MISMATCH',
      'PAIRING_REQUIRED',
      'DEVICE_IDENTITY_REQUIRED',
      'CONTROL_UI_DEVICE_IDENTITY_REQUIRED',
    };
    if (resolvedCode != null && nonRetryableCodes.contains(resolvedCode)) {
      return false;
    }
    if (resolvedDetailCode != null &&
        nonRetryableDetailCodes.contains(resolvedDetailCode)) {
      return false;
    }
    return true;
  }

  bool isPairingRequiredErrorInternal(String? code, String? detailCode) {
    final resolvedCode = code?.trim().toUpperCase();
    final resolvedDetailCode = detailCode?.trim().toUpperCase();
    return resolvedCode == 'NOT_PAIRED' ||
        resolvedDetailCode == 'PAIRING_REQUIRED';
  }

  Future<void> closeSocketInternal(GatewayRuntime runtime) async {
    runtime.reconnectTimerInternal?.cancel();
    final subscription = runtime.socketSubscriptionInternal;
    runtime.socketSubscriptionInternal = null;
    await subscription?.cancel();
    await runtime.channelInternal?.sink.close();
    runtime.channelInternal = null;
    failPendingInternal(
      runtime,
      GatewayRuntimeException('socket reset', code: 'SOCKET_RESET'),
    );
  }

  void appendLogInternal(
    GatewayRuntime runtime,
    String level,
    String category,
    String message,
  ) {
    runtime.logsInternal.add(
      RuntimeLogEntry(
        timestampMs: DateTime.now().millisecondsSinceEpoch,
        level: level,
        category: category,
        message: message,
      ),
    );
    const maxLogEntries = 250;
    if (runtime.logsInternal.length > maxLogEntries) {
      runtime.logsInternal.removeRange(
        0,
        runtime.logsInternal.length - maxLogEntries,
      );
    }
    runtime.notifyListeners();
  }

  String connectAuthSummaryInternal({
    required String mode,
    required List<String> fields,
    required List<String> sources,
  }) {
    final resolvedFields = fields.isEmpty ? 'none' : fields.join(', ');
    final resolvedSources = sources.isEmpty ? 'none' : sources.join(' · ');
    return '$mode | fields: $resolvedFields | sources: $resolvedSources';
  }

  void failPendingInternal(GatewayRuntime runtime, Object error) {
    final values = runtime.pendingInternal.values.toList(growable: false);
    runtime.pendingInternal.clear();
    for (final completer in values) {
      if (!completer.isCompleted) {
        completer.completeError(error);
      }
    }
  }

  String resolveClientIdInternal() {
    return resolveGatewayClientId();
  }

  Future<RuntimePackageInfo> loadPackageInfoInternal() async {
    try {
      final info = await PackageInfo.fromPlatform();
      return RuntimePackageInfo(
        appName: info.appName,
        packageName: info.packageName,
        version: info.version,
        buildNumber: info.buildNumber,
      );
    } catch (e, stackTrace) { debugPrint('Error: $e\n$stackTrace');
      return const RuntimePackageInfo(
        appName: kSystemAppName,
        packageName: 'plus.svc.xworkmate',
        version: kAppVersion,
        buildNumber: kAppBuildNumber,
      );
    }
  }

  Future<RuntimeDeviceInfo> loadDeviceInfoInternal() async {
    final plugin = DeviceInfoPlugin();
    try {
      if (Platform.isIOS) {
        final info = await plugin.iosInfo;
        return RuntimeDeviceInfo(
          platform: 'ios',
          platformVersion: info.systemVersion,
          deviceFamily: info.model,
          modelIdentifier: info.utsname.machine,
        );
      }
      if (Platform.isMacOS) {
        final info = await plugin.macOsInfo;
        return RuntimeDeviceInfo(
          platform: 'macos',
          platformVersion:
              '${info.majorVersion}.${info.minorVersion}.${info.patchVersion}',
          deviceFamily: 'Mac',
          modelIdentifier: info.model,
        );
      }
      if (Platform.isAndroid) {
        final info = await plugin.androidInfo;
        return RuntimeDeviceInfo(
          platform: 'android',
          platformVersion: info.version.release,
          deviceFamily: info.model,
          modelIdentifier: info.id,
        );
      }
      if (Platform.isWindows) {
        final info = await plugin.windowsInfo;
        return RuntimeDeviceInfo(
          platform: 'windows',
          platformVersion: info.displayVersion,
          deviceFamily: 'Windows',
          modelIdentifier: info.computerName,
        );
      }
      if (Platform.isLinux) {
        final info = await plugin.linuxInfo;
        return RuntimeDeviceInfo(
          platform: 'linux',
          platformVersion: info.version ?? '',
          deviceFamily: 'Linux',
          modelIdentifier: info.machineId ?? 'linux',
        );
      }
    } catch (e, stackTrace) { debugPrint('Error: $e\n$stackTrace');
      // Fall through to generic info.
    }
    return RuntimeDeviceInfo(
      platform: Platform.operatingSystem,
      platformVersion: Platform.operatingSystemVersion,
      deviceFamily: Platform.operatingSystem,
      modelIdentifier: Platform.localHostname,
    );
  }
}

class GatewaySetupPayload {
  const GatewaySetupPayload({
    required this.host,
    required this.port,
    required this.tls,
    required this.token,
    required this.password,
  });

  final String host;
  final int port;
  final bool tls;
  final String token;
  final String password;
}

GatewaySetupPayload? decodeGatewaySetupCode(String rawInput) {
  final trimmed = rawInput.trim();
  if (trimmed.isEmpty) {
    return null;
  }
  final candidate = resolveSetupCodeCandidateInternal(trimmed);
  final direct = decodeSetupPayloadJsonInternal(candidate);
  if (direct != null) {
    return direct;
  }
  try {
    final normalized = candidate.replaceAll('-', '+').replaceAll('_', '/');
    final padded = normalized + '=' * ((4 - normalized.length % 4) % 4);
    final decoded = utf8.decode(base64.decode(padded));
    return decodeSetupPayloadJsonInternal(decoded);
  } catch (e, stackTrace) { debugPrint('Error: $e\n$stackTrace');
    return null;
  }
}

GatewaySetupPayload? decodeSetupPayloadJsonInternal(String raw) {
  try {
    final json = jsonDecode(raw) as Map<String, dynamic>;
    final url = stringValue(json['url']);
    final host = stringValue(json['host']);
    final port = intValue(json['port']);
    final tls = boolValue(json['tls']);
    final resolved = parseGatewayEndpoint(
      url ?? composeManualUrlInternal(host, port, tls),
    );
    if (resolved == null) {
      return null;
    }
    return GatewaySetupPayload(
      host: resolved.$1,
      port: resolved.$2,
      tls: resolved.$3,
      token: stringValue(json['token']) ?? '',
      password: stringValue(json['password']) ?? '',
    );
  } catch (e, stackTrace) { debugPrint('Error: $e\n$stackTrace');
    return null;
  }
}

String resolveSetupCodeCandidateInternal(String raw) {
  try {
    final decoded = jsonDecode(raw);
    if (decoded is Map<String, dynamic>) {
      return stringValue(decoded['setupCode']) ?? raw;
    }
  } catch (e, stackTrace) { debugPrint('Error: $e\n$stackTrace');
    // Leave raw as-is.
  }
  return raw;
}

(String, int, bool)? parseGatewayEndpoint(String? rawInput) {
  final raw = rawInput?.trim() ?? '';
  if (raw.isEmpty) {
    return null;
  }
  final normalized = raw.contains('://') ? raw : 'https://$raw';
  final uri = Uri.tryParse(normalized);
  final host = uri?.host.trim() ?? '';
  if (host.isEmpty) {
    return null;
  }
  final scheme = uri?.scheme.trim().toLowerCase() ?? 'https';
  final tls = switch (scheme) {
    'ws' || 'http' => false,
    _ => true,
  };
  final parsedPort = uri?.port;
  final port = parsedPort != null && parsedPort >= 1 && parsedPort <= 65535
      ? parsedPort
      : (tls ? 443 : 18789);
  return (host, port, tls);
}

String? composeManualUrlInternal(String? host, int? port, bool? tls) {
  final trimmedHost = host?.trim() ?? '';
  if (trimmedHost.isEmpty) {
    return null;
  }
  final resolvedPort = port ?? 18789;
  final scheme = tls == false ? 'http' : 'https';
  return '$scheme://$trimmedHost:$resolvedPort';
}

Map<String, dynamic> asMap(Object? value) {
  if (value is Map<String, dynamic>) {
    return value;
  }
  if (value is Map) {
    return value.cast<String, dynamic>();
  }
  return const <String, dynamic>{};
}

List<dynamic> asList(Object? value) {
  if (value is List<dynamic>) {
    return value;
  }
  if (value is List) {
    return value.cast<dynamic>();
  }
  return const <dynamic>[];
}

String? stringValue(Object? value) {
  if (value is String) {
    final trimmed = value.trim();
    return trimmed.isEmpty ? null : trimmed;
  }
  return null;
}

bool? boolValue(Object? value) {
  if (value is bool) {
    return value;
  }
  if (value is String) {
    switch (value.trim().toLowerCase()) {
      case 'true':
        return true;
      case 'false':
        return false;
    }
  }
  return null;
}

int? intValue(Object? value) {
  if (value is int) {
    return value;
  }
  if (value is double) {
    return value.toInt();
  }
  if (value is String) {
    return int.tryParse(value);
  }
  return null;
}

double? doubleValue(Object? value) {
  if (value is double) {
    return value;
  }
  if (value is int) {
    return value.toDouble();
  }
  if (value is String) {
    return double.tryParse(value);
  }
  return null;
}

List<String> stringList(Object? value) {
  return asList(
    value,
  ).map(stringValue).whereType<String>().toList(growable: false);
}

String extractMessageText(Map<String, dynamic> message) {
  final directContent = message['content'];
  if (directContent is String) {
    return directContent;
  }
  final parts = <String>[];
  for (final part in asList(directContent)) {
    final map = asMap(part);
    final text = stringValue(map['text']) ?? stringValue(map['thinking']);
    if (text != null && text.isNotEmpty) {
      parts.add(text);
      continue;
    }
    final nestedContent = map['content'];
    if (nestedContent is String && nestedContent.trim().isNotEmpty) {
      parts.add(nestedContent.trim());
    }
  }
  return parts.join('\n').trim();
}

String randomIdInternal() {
  final random = Random.secure();
  final timestamp = DateTime.now().microsecondsSinceEpoch.toRadixString(16);
  final suffix = List<int>.generate(
    6,
    (_) => random.nextInt(256),
  ).map((value) => value.toRadixString(16).padLeft(2, '0')).join();
  return '$timestamp-$suffix';
}

class RpcResponseInternal {
  const RpcResponseInternal({
    required this.ok,
    required this.payload,
    required this.error,
  });

  final bool ok;
  final dynamic payload;
  final Map<String, dynamic> error;
}
