import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../database/database.dart'; import '../../features/records/records_service.dart'; import '../auth/auth_providers.dart'; import '../auth/auth_state.dart'; import '../cloud/firebase_account_sync_service.dart'; import '../cloud/account_cloud_cleanup_service.dart'; import '../auth/account_lifecycle_service.dart'; import '../github/github_auth_service.dart'; import '../github/github_sync_service.dart'; import 'firebase_sync_provider.dart'; import 'github_sync_provider.dart'; import 'r2_upload_service.dart'; import 'cloud_restore_controller.dart'; import 'cloud_restore_service.dart'; import 'sync_bundle_worker.dart'; import 'sync_manager.dart'; import 'sync_models.dart'; import 'sync_outbox.dart'; final r2UploadServiceProvider = Provider( (ref) => R2UploadService(), ); final syncBundleWorkerProvider = Provider( (ref) => SyncBundleWorker(ref.watch(databaseProvider)), ); final cloudRestoreServiceProvider = Provider( (ref) => CloudRestoreService( db: ref.watch(databaseProvider), authService: ref.watch(authServiceProvider), accountSyncService: ref.watch(firebaseAccountSyncServiceProvider), ), ); final cloudRestoreControllerProvider = StateNotifierProvider( (ref) => CloudRestoreController( restoreService: ref.watch(cloudRestoreServiceProvider), authService: ref.watch(authServiceProvider), recordsService: ref.watch(recordsServiceProvider.notifier), db: ref.watch(databaseProvider), ), ); /// Cloud sync state is account-scoped. The Firebase UID is intentionally part /// of the persisted namespace so signing out and into another account cannot /// inherit the previous account's synced/pending/failed ride state. final syncOutboxProvider = Provider((ref) { final prefs = ref.watch(sharedPreferencesProvider); final user = ref.watch(currentUserProvider); final namespace = user?.uid ?? 'local'; return SyncOutbox(prefs, storageNamespace: namespace); }); final firebaseSyncProvider = Provider( (ref) => FirebaseSyncProvider( db: ref.watch(databaseProvider), authService: ref.watch(authServiceProvider), bundleWorker: ref.watch(syncBundleWorkerProvider), r2UploadService: ref.watch(r2UploadServiceProvider), ), ); final firebaseAccountSyncServiceProvider = Provider( (ref) => FirebaseAccountSyncService( db: ref.watch(databaseProvider), auth: ref.watch(authServiceProvider), ), ); final accountCloudCleanupServiceProvider = Provider( (ref) => AccountCloudCleanupService(auth: ref.watch(authServiceProvider)), ); final accountLifecycleServiceProvider = Provider( (ref) => AccountLifecycleService( auth: ref.watch(authServiceProvider), cloud: ref.watch(accountCloudCleanupServiceProvider), db: ref.watch(databaseProvider), ), ); final githubSyncProvider = Provider( (ref) => GitHubSyncProvider( service: ref.watch(githubSyncServiceProvider), auth: ref.watch(githubAuthServiceProvider), ), ); final syncManagerProvider = ChangeNotifierProvider((ref) { final manager = SyncManager( ref.watch(syncOutboxProvider), initialProviders: [ ref.watch(firebaseSyncProvider), ref.watch(githubSyncProvider), ], defaultProviderId: 'firebase', db: ref.watch(databaseProvider), authService: ref.watch(authServiceProvider), ); final accountSync = ref.watch(firebaseAccountSyncServiceProvider); // The provider is rebuilt when the account UID changes. If the new account // already has persisted auth-blocked work, resume it immediately rather than // waiting for another auth event that may never arrive. if (ref.read(authNotifierProvider).isAuthenticated) { manager.retryAuthRequired(); } ref.listen(authNotifierProvider, (previous, next) { if (next.isAuthenticated && previous?.isAuthenticated != true) { manager.retryAuthRequired(); accountSync.syncAccount().then( (_) {}, onError: (_) { // Profile/garage synchronization is downstream of riding and must // never interrupt telemetry or ride persistence. }, ); } }); ref.onDispose(manager.dispose); return manager; }); final syncStateProvider = Provider( (ref) => ref.watch(syncManagerProvider).currentState, ); final pendingSyncCountProvider = Provider( (ref) => ref.watch(syncStateProvider).pendingCount, ); final isSyncProcessingProvider = Provider( (ref) => ref.watch(syncStateProvider).isProcessing, );