import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_typography.dart'; import '../../rides/ride_state_machine.dart'; import '../../../models/recording_health_status.dart'; /// Authoritative ride status badge/chip. /// /// Subscribes strictly to /// [rideStateMachineProvider.select((s) => (s.state, s.healthStatus))] - the /// narrowest selection that can render a truthful status claim. RideState decides /// which lifecycle branch applies; the RID-3C-1 health status decides the claim. /// Both fields change only on real lifecycle or health transitions, so this still /// never rebuilds on 20 Hz telemetry ticks. class RideStatusChip extends ConsumerWidget { const RideStatusChip({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { // RID-3C-2: RideState decides WHICH lifecycle branch applies; the RID-3C-1 // health status decides the CLAIM inside an active ride. This widget performs // no health calculation of its own - recordingActive is already the product of // canReportRecording plus the frozen evidence thresholds, so RECORDING can // never be claimed from RideState or service presence alone. // // The select stays narrow (state + healthStatus only), so the chip still does // NOT rebuild on 20 Hz telemetry ticks. healthStatus changes only on real // warm-up / degradation / recovery / finalization transitions. final (state, health) = ref.watch( rideStateMachineProvider.select((s) => (s.state, s.healthStatus)), ); final isActiveRide = state == RideState.riding; final isPaused = state == RideState.paused; // The glow/emphasis treatment belongs to a genuine healthy recording claim // only, never to a warm-up or degraded ride. final claimsRecording = isActiveRide && health == RecordingHealthStatus.recordingActive; final theme = Theme.of(context); final isDark = theme.brightness == Brightness.dark; final Color badgeColor; final String label; if (isActiveRide) { switch (health) { case RecordingHealthStatus.recordingActive: // Existing healthy/active treatment for this chip, preserved. badgeColor = AppColors.raceRed; label = 'RECORDING'; break; case RecordingHealthStatus.recordingArmed: case RecordingHealthStatus.recordingStarting: // Ride exists but required evidence has not completed yet. Neutral / // informational; the Stop action remains available elsewhere. badgeColor = isDark ? AppColors.cyberCyan : AppColors.lightPrimary; label = 'WARMING UP'; break; case RecordingHealthStatus.recordingFinalizing: // RID-3B-1 finalization has priority and is never shown as RECORDING. badgeColor = isDark ? AppColors.cyberCyan : AppColors.lightPrimary; label = 'FINALIZING'; break; case RecordingHealthStatus.recordingDegraded: case RecordingHealthStatus.recordingFailed: // Warning, NOT danger: DEGRADED means the ride is still active and still // stoppable - not ended, discarded, failed or service-stopped. badgeColor = AppColors.warningAmber; label = 'DEGRADED'; break; case RecordingHealthStatus.recordingComplete: // Unreachable alongside an active ride; must not present as recording. badgeColor = theme.colorScheme.onSurfaceVariant; label = 'STANDBY'; break; } } else if (isPaused) { badgeColor = AppColors.warningAmber; label = 'PAUSED'; } else if (state == RideState.ready) { badgeColor = isDark ? AppColors.cyberCyan : AppColors.lightPrimary; label = 'READY'; } else { badgeColor = theme.colorScheme.onSurfaceVariant; label = 'STANDBY'; } return Container( padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 4), decoration: BoxDecoration( color: badgeColor.withValues(alpha: claimsRecording ? 0.18 : 0.10), borderRadius: BorderRadius.circular(6), border: Border.all( color: badgeColor.withValues(alpha: claimsRecording ? 0.8 : 0.35), width: 1.0, ), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Container( width: 7, height: 7, decoration: BoxDecoration( shape: BoxShape.circle, color: badgeColor, boxShadow: claimsRecording ? [ BoxShadow( color: badgeColor.withValues(alpha: 0.6), blurRadius: 6, ), ] : null, ), ), const SizedBox(width: 6), Text( label, style: TextStyle( fontFamily: AppTypography.fontTelemetry, fontSize: 10, fontWeight: FontWeight.w800, color: badgeColor, letterSpacing: 0.6, ), ), ], ), ); } }