import 'package:flutter/foundation.dart'; import '../models/sync_data.dart'; import '../models/sync_enums.dart'; import '../services/webdav_sync_service.dart'; import 'supplement_provider.dart'; /// Provider for managing sync operations with WebDAV servers class SyncProvider with ChangeNotifier { final WebDAVSyncService _syncService = WebDAVSyncService(); final SupplementProvider _supplementProvider; SyncOperationStatus _status = SyncOperationStatus.idle; SyncResult? _lastSyncResult; DateTime? _lastSyncTime; String? _currentError; bool _isAutoSyncEnabled = false; bool _autoSyncOnDataChanges = true; SyncFrequency _syncFrequency = SyncFrequency.hourly; ConflictResolutionStrategy _conflictStrategy = ConflictResolutionStrategy.manual; List _pendingConflicts = []; double _syncProgress = 0.0; // WebDAV Configuration String? _serverUrl; String? _username; String? _detectedServerType; String? _finalWebdavUrl; String? _syncFolderName; bool _isConfigured = false; SyncProvider(this._supplementProvider); // Getters SyncOperationStatus get status => _status; SyncResult? get lastSyncResult => _lastSyncResult; DateTime? get lastSyncTime => _lastSyncTime; String? get currentError => _currentError; bool get isAutoSyncEnabled => _isAutoSyncEnabled; bool get autoSyncOnDataChanges => _autoSyncOnDataChanges; SyncFrequency get syncFrequency => _syncFrequency; ConflictResolutionStrategy get conflictStrategy => _conflictStrategy; List get pendingConflicts => List.unmodifiable(_pendingConflicts); double get syncProgress => _syncProgress; String? get serverUrl => _serverUrl; String? get username => _username; String? get detectedServerType => _detectedServerType; String? get finalWebdavUrl => _finalWebdavUrl; String? get syncFolderName => _syncFolderName; bool get isConfigured => _isConfigured; bool get isSyncing => _status == SyncOperationStatus.syncing; bool get hasError => _currentError != null; bool get hasPendingConflicts => _pendingConflicts.isNotEmpty; /// Initialize the sync provider Future initialize() async { await _syncService.initialize(); _isConfigured = await _syncService.isConfigured(); _lastSyncTime = await _syncService.getLastSyncTime(); if (_isConfigured) { // Load existing configuration _serverUrl = await _syncService.getServerUrl(); _username = await _syncService.getUsername(); _syncFolderName = await _syncService.getSyncFolderName(); _finalWebdavUrl = await _syncService.getLastWorkingUrl(); _detectedServerType = _detectServerTypeFromUrl(_finalWebdavUrl ?? _serverUrl ?? ''); } // Set up data change callback on supplement provider _supplementProvider.setOnDataChangedCallback(() { triggerAutoSyncOnDataChange(); }); notifyListeners(); } /// Configure WebDAV connection Future configure({ required String serverUrl, required String username, String? password, String? deviceName, String? syncFolderName, }) async { try { _setStatus(SyncOperationStatus.syncing); _clearError(); final success = await _syncService.configure( baseUrl: serverUrl, username: username, password: password, deviceName: deviceName, syncFolderName: syncFolderName ?? 'Supplements', ); if (success) { _serverUrl = serverUrl; _username = username; _syncFolderName = syncFolderName ?? 'Supplements'; _isConfigured = true; // Get server detection info _finalWebdavUrl = await _syncService.getLastWorkingUrl(); _detectedServerType = _detectServerTypeFromUrl(_finalWebdavUrl ?? serverUrl); _setStatus(SyncOperationStatus.success); } else { _setError('Failed to configure WebDAV connection', SyncOperationStatus.authenticationError); } return success; } catch (e) { _setError(e.toString(), SyncOperationStatus.authenticationError); return false; } } /// Test WebDAV connection Future testConnection() async { if (!_isConfigured) return false; try { _clearError(); return await _syncService.testConnection(); } catch (e) { _setError(e.toString(), SyncOperationStatus.networkError); return false; } } /// Clear WebDAV configuration Future clearConfiguration() async { await _syncService.clearConfiguration(); _serverUrl = null; _username = null; _detectedServerType = null; _finalWebdavUrl = null; _syncFolderName = null; _isConfigured = false; _setStatus(SyncOperationStatus.idle); _clearError(); } /// Perform manual sync Future performManualSync() async { if (!_isConfigured || _status == SyncOperationStatus.syncing) { return false; } try { _setStatus(SyncOperationStatus.syncing); _clearError(); _syncProgress = 0.0; // Check connectivity first if (!await _syncService.hasConnectivity()) { _setError('No internet connection', SyncOperationStatus.networkError); return false; } _syncProgress = 0.2; notifyListeners(); // Perform the sync final result = await _syncService.performSync(); _lastSyncResult = result; _lastSyncTime = result.timestamp; _syncProgress = 0.8; notifyListeners(); if (result.success) { if (result.conflicts.isNotEmpty) { _pendingConflicts = result.conflicts; _setStatus(SyncOperationStatus.conflictsDetected); } else { _setStatus(SyncOperationStatus.success); } // Reload local data after successful sync await _supplementProvider.loadSupplements(); await _supplementProvider.loadTodayIntakes(); } else { _setError(result.error ?? 'Sync failed', result.status); } _syncProgress = 1.0; notifyListeners(); return result.success; } catch (e) { _setError(e.toString(), SyncOperationStatus.serverError); return false; } } /// Resolve a conflict Future resolveConflict(String syncId, ConflictResolution resolution) async { _pendingConflicts.removeWhere((conflict) => conflict.syncId == syncId); // TODO: Implement actual conflict resolution logic // This would involve updating the local database with the chosen resolution if (_pendingConflicts.isEmpty && _status == SyncOperationStatus.conflictsDetected) { _setStatus(SyncOperationStatus.success); } notifyListeners(); } /// Resolve all conflicts using the configured strategy Future resolveAllConflicts() async { if (_conflictStrategy == ConflictResolutionStrategy.manual) { return; // Manual conflicts need individual resolution } final resolvedConflicts = []; for (final conflict in _pendingConflicts) { ConflictResolution resolution; switch (_conflictStrategy) { case ConflictResolutionStrategy.preferLocal: resolution = ConflictResolution.useLocal; break; case ConflictResolutionStrategy.preferRemote: resolution = ConflictResolution.useRemote; break; case ConflictResolutionStrategy.preferNewer: resolution = conflict.isLocalNewer ? ConflictResolution.useLocal : ConflictResolution.useRemote; break; case ConflictResolutionStrategy.manual: continue; // Skip manual conflicts } await resolveConflict(conflict.syncId, resolution); resolvedConflicts.add(conflict); } if (resolvedConflicts.isNotEmpty) { // Perform another sync to apply resolutions await performManualSync(); } } /// Enable or disable auto sync Future setAutoSyncEnabled(bool enabled) async { _isAutoSyncEnabled = enabled; notifyListeners(); if (enabled) { _scheduleNextAutoSync(); } // TODO: Cancel existing timers when disabled } /// Set sync frequency Future setSyncFrequency(SyncFrequency frequency) async { _syncFrequency = frequency; notifyListeners(); if (_isAutoSyncEnabled) { _scheduleNextAutoSync(); } } /// Set conflict resolution strategy Future setConflictResolutionStrategy(ConflictResolutionStrategy strategy) async { _conflictStrategy = strategy; notifyListeners(); } /// Enable or disable auto sync on data changes Future setAutoSyncOnDataChanges(bool enabled) async { _autoSyncOnDataChanges = enabled; notifyListeners(); } /// Trigger sync automatically when data changes (if enabled) Future triggerAutoSyncOnDataChange() async { if (!_autoSyncOnDataChanges || !_isConfigured || _status == SyncOperationStatus.syncing) { return; } // Perform sync without blocking the UI performManualSync(); } /// Get sync statistics from last result SyncStatistics? get lastSyncStatistics => _lastSyncResult?.statistics; /// Get formatted last sync time String get formattedLastSyncTime { if (_lastSyncTime == null) return 'Never'; final now = DateTime.now(); final difference = now.difference(_lastSyncTime!); if (difference.inMinutes < 1) { return 'Just now'; } else if (difference.inMinutes < 60) { return '${difference.inMinutes}m ago'; } else if (difference.inHours < 24) { return '${difference.inHours}h ago'; } else { return '${difference.inDays}d ago'; } } /// Get sync status message String get statusMessage { switch (_status) { case SyncOperationStatus.idle: return 'Ready to sync'; case SyncOperationStatus.syncing: return 'Syncing... ${(_syncProgress * 100).round()}%'; case SyncOperationStatus.success: return 'Sync completed successfully'; case SyncOperationStatus.networkError: return 'Network connection failed'; case SyncOperationStatus.authenticationError: return 'Authentication failed'; case SyncOperationStatus.serverError: return 'Server error occurred'; case SyncOperationStatus.conflictsDetected: return '${_pendingConflicts.length} conflict(s) need resolution'; case SyncOperationStatus.cancelled: return 'Sync was cancelled'; } } /// Get device info Future> getDeviceInfo() async { return await _syncService.getDeviceInfo(); } // Private methods void _setStatus(SyncOperationStatus status) { _status = status; notifyListeners(); } void _setError(String error, SyncOperationStatus status) { _currentError = error; _status = status; _syncProgress = 0.0; notifyListeners(); } void _clearError() { _currentError = null; notifyListeners(); } void _scheduleNextAutoSync() { if (!_isAutoSyncEnabled || _syncFrequency == SyncFrequency.manual) { return; } // TODO: Implement actual scheduling logic using Timer or WorkManager // This would schedule the next automatic sync based on the frequency if (kDebugMode) { print('Next auto sync scheduled for ${_syncFrequency.displayName}'); } } /// Detect server type from URL for display purposes String _detectServerTypeFromUrl(String url) { final lowerUrl = url.toLowerCase(); if (lowerUrl.contains('/remote.php/dav/files/')) { return 'Nextcloud'; } else if (lowerUrl.contains('/remote.php/webdav/')) { return 'ownCloud'; } else if (lowerUrl.contains('nextcloud')) { return 'Nextcloud'; } else if (lowerUrl.contains('owncloud')) { return 'ownCloud'; } else if (lowerUrl.contains('/webdav/') || lowerUrl.contains('/dav/')) { return 'Generic WebDAV'; } else { return 'WebDAV Server'; } } @override void dispose() { // TODO: Cancel any pending timers or background tasks super.dispose(); } } /// Enum for conflict resolution choices enum ConflictResolution { useLocal, useRemote, merge, // For future implementation } /// Extension methods for ConflictResolution extension ConflictResolutionExtension on ConflictResolution { String get displayName { switch (this) { case ConflictResolution.useLocal: return 'Use Local Version'; case ConflictResolution.useRemote: return 'Use Remote Version'; case ConflictResolution.merge: return 'Merge Changes'; } } }