import 'package:flutter/material.dart'; import '../../theme/app_theme.dart'; import '../models/sync_run_event.dart'; String formatRelativeTime(DateTime startedAt, {DateTime? now}) { final DateTime reference = (now ?? DateTime.now()).toUtc(); final Duration delta = reference.difference(startedAt.toUtc()); if (delta.inMinutes < 1) { return 'just now'; } if (delta.inHours < 1) { return '${delta.inMinutes}m ago'; } if (delta.inDays < 1) { return '${delta.inHours}h ago'; } return '${delta.inDays}d ago'; } String formatMarketHistorySlotWire(DateTime value) { final DateTime utc = value.toUtc(); final int slotHour = (utc.hour ~/ 4) * 4; final DateTime slotStart = DateTime.utc( utc.year, utc.month, utc.day, slotHour, ); String two(int n) => n.toString().padLeft(2, '0'); return '${slotStart.year.toString().padLeft(4, '0')}-' '${two(slotStart.month)}-${two(slotStart.day)}T' '${two(slotStart.hour)}:${two(slotStart.minute)}:${two(slotStart.second)}Z'; } String formatUtcTimestamp(DateTime? value) { if (value == null) { return '—'; } final DateTime utc = value.toUtc(); final String hour = utc.hour.toString().padLeft(2, '0'); final String minute = utc.minute.toString().padLeft(2, '0'); return '${utc.year}-${utc.month.toString().padLeft(2, '0')}-' '${utc.day.toString().padLeft(2, '0')} $hour:$minute UTC'; } String formatLocalTimestamp(DateTime? value) { if (value == null) { return '—'; } final DateTime local = value.toLocal(); final String hour = local.hour.toString().padLeft(2, '0'); final String minute = local.minute.toString().padLeft(2, '0'); return '${local.year}-${local.month.toString().padLeft(2, '0')}-' '${local.day.toString().padLeft(2, '0')} $hour:$minute'; } String formatDurationMs(int? durationMs) { if (durationMs == null) { return '—'; } if (durationMs < 1000) { return '${durationMs}ms'; } return '${(durationMs / 1000).toStringAsFixed(1)}s'; } String shortStatusLabel(SyncRunEvent event) { return switch (event.status) { SyncRunStatus.success => 'Success', SyncRunStatus.failed => 'Failed', SyncRunStatus.partial => 'Partial success', SyncRunStatus.inProgress => 'In progress', }; } IconData severityIcon(SyncRunSeverity severity) { return switch (severity) { SyncRunSeverity.ok => Icons.check_circle_outline, SyncRunSeverity.warning => Icons.schedule, SyncRunSeverity.error => Icons.error_outline, SyncRunSeverity.rateLimit => Icons.speed, }; } Color severityColor(SyncRunSeverity severity) { return switch (severity) { SyncRunSeverity.ok => AppColors.success, SyncRunSeverity.warning => Colors.orange, SyncRunSeverity.error => const Color(0xFFF87171), SyncRunSeverity.rateLimit => Colors.amber, }; } String? parseHttpStatus(String? error) { if (error == null || error.trim().isEmpty) { return null; } final RegExpMatch? match = RegExp(r'\b(\d{3})\b').firstMatch(error); return match?.group(1); }