import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:collection/collection.dart'; import 'package:pshared/models/describable.dart'; import 'package:pshared/models/organization/bound.dart'; import 'package:pshared/models/payment/methods/data.dart'; import 'package:pshared/models/payment/methods/type.dart'; import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/permissions/bound.dart'; import 'package:pshared/models/recipient/payment_method_draft.dart'; import 'package:pshared/models/storable.dart'; import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/recipient/provider.dart'; import 'package:pshared/service/recipient/pmethods.dart'; import 'package:pshared/utils/exception.dart'; class RecipientMethodsCacheProvider extends ChangeNotifier { late OrganizationsProvider _organizations; final Map> _methodsByRecipient = {}; final Map _loadingByRecipient = {}; final Map _errorByRecipient = {}; Set _trackedRecipients = {}; List methodsForRecipient(String recipientId) => List.unmodifiable(_methodsByRecipient[recipientId] ?? const []); bool isLoadingFor(String recipientId) => _loadingByRecipient[recipientId] == true; Object? errorFor(String recipientId) => _errorByRecipient[recipientId]; bool hasMethodsFor(String recipientId) => _methodsByRecipient.containsKey(recipientId); bool isTrackingRecipient(String recipientId) => _trackedRecipients.contains(recipientId); void updateProviders(OrganizationsProvider organizations, RecipientsProvider recipients) { _organizations = organizations; if (!_organizations.isOrganizationSet) return; final nextRecipients = recipients.items.map((r) => r.id).toSet(); if (setEquals(_trackedRecipients, nextRecipients)) return; final removed = _trackedRecipients.difference(nextRecipients); for (final recipientId in removed) { _methodsByRecipient.remove(recipientId); _loadingByRecipient.remove(recipientId); _errorByRecipient.remove(recipientId); } final added = nextRecipients.difference(_trackedRecipients); _trackedRecipients = nextRecipients; for (final recipientId in added) { unawaited(_loadRecipientMethods(recipientId)); } } Future refreshRecipient(String recipientId) => _loadRecipientMethods(recipientId, force: true); Future syncRecipientMethods({ required String recipientId, required List methods, required Map names, }) async { await _ensureLoaded(recipientId); final current = List.from(_methodsByRecipient[recipientId] ?? const []); final currentById = {for (final method in current) method.id: method}; final desired = methods.where((m) => m.data != null).toList(); final desiredExisting = desired.where((m) => m.existing != null).toList(); final desiredExistingIds = desiredExisting.map((m) => m.existing!.id).toSet(); for (final method in current.toList()) { if (!desiredExistingIds.contains(method.id)) { await PaymentMethodService.delete(method); current.removeWhere((m) => m.id == method.id); } } for (final entry in desiredExisting) { final existing = entry.existing; final data = entry.data; if (existing == null || data == null) continue; final currentMethod = currentById[existing.id] ?? existing; final updated = currentMethod.copyWith(data: data); final updatedList = await PaymentMethodService.update(updated); final updatedMethod = updatedList.firstWhereOrNull((m) => m.id == updated.id) ?? updated; final index = current.indexWhere((m) => m.id == updatedMethod.id); if (index != -1) { current[index] = updatedMethod; } } for (final entry in desired.where((m) => m.existing == null)) { final data = entry.data; if (data == null) continue; final type = entry.type; final created = await _createMethod( recipientId: recipientId, data: data, name: names[type] ?? type.name, ); current.add(created); } _methodsByRecipient[recipientId] = _sortedMethods(current); notifyListeners(); } Future _ensureLoaded(String recipientId) async { if (_methodsByRecipient.containsKey(recipientId)) return; await _loadRecipientMethods(recipientId); } Future _loadRecipientMethods(String recipientId, {bool force = false}) async { if (!force && _loadingByRecipient[recipientId] == true) return; _setLoading(recipientId, true); try { final list = await PaymentMethodService.list(_organizations.current.id, recipientId); _methodsByRecipient[recipientId] = _sortedMethods(list); _errorByRecipient.remove(recipientId); } catch (e) { _errorByRecipient[recipientId] = toException(e); } finally { _setLoading(recipientId, false); } } Future _createMethod({ required String recipientId, required PaymentMethodData data, required String name, }) async { final organizationRef = _organizations.current.id; final method = PaymentMethod( storable: newStorable(), permissionBound: newPermissionBound( organizationBound: newOrganizationBound(organizationRef: organizationRef), ), recipientRef: recipientId, data: data, describable: newDescribable(name: name), ); final created = await PaymentMethodService.create(organizationRef, method); return created.first; } void _setLoading(String recipientId, bool value) { _loadingByRecipient[recipientId] = value; notifyListeners(); } List _sortedMethods(List methods) => methods.toList()..sort((a, b) => a.storable.createdAt.compareTo(b.storable.createdAt)); }