import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:pshared/models/payment/payment.dart'; import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/resource.dart'; import 'package:pshared/service/payment/service.dart'; import 'package:pshared/utils/exception.dart'; class PaymentsProvider with ChangeNotifier { OrganizationsProvider? _organizations; String? _loadedOrganizationRef; Resource> _resource = Resource(data: []); bool _isLoaded = false; bool _isLoadingMore = false; String? _nextCursor; int? _limit; String? _sourceRef; String? _destinationRef; List? _states; int _opSeq = 0; Resource> get resource => _resource; List get payments => _resource.data ?? []; bool get isLoading => _resource.isLoading; Exception? get error => _resource.error; bool get isReady => _isLoaded && !_resource.isLoading && _resource.error == null; bool get isLoadingMore => _isLoadingMore; String? get nextCursor => _nextCursor; bool get canLoadMore => _nextCursor != null && _nextCursor!.isNotEmpty; void update(OrganizationsProvider organizations) { _organizations = organizations; if (!organizations.isOrganizationSet) { reset(); return; } final orgRef = organizations.current.id; if (_loadedOrganizationRef != orgRef) { _loadedOrganizationRef = orgRef; unawaited(refresh()); } } Future refresh({ int? limit, String? sourceRef, String? destinationRef, List? states, }) async { final org = _organizations; if (org == null || !org.isOrganizationSet) return; _limit = limit; _sourceRef = _normalize(sourceRef); _destinationRef = _normalize(destinationRef); _states = _normalizeStates(states); _nextCursor = null; _isLoadingMore = false; final seq = ++_opSeq; _applyResource(_resource.copyWith(isLoading: true, error: null), notify: true); try { final page = await PaymentService.listPage( org.current.id, limit: _limit, cursor: null, sourceRef: _sourceRef, destinationRef: _destinationRef, states: _states, ); if (seq != _opSeq) return; _isLoaded = true; _nextCursor = _normalize(page.nextCursor); _applyResource( Resource(data: page.items, isLoading: false, error: null), notify: true, ); } catch (e) { if (seq != _opSeq) return; _applyResource( _resource.copyWith(isLoading: false, error: toException(e)), notify: true, ); } } Future loadMore() async { final org = _organizations; if (org == null || !org.isOrganizationSet) return; if (_isLoadingMore || _resource.isLoading) return; final cursor = _normalize(_nextCursor); if (cursor == null) return; final seq = _opSeq; _isLoadingMore = true; _applyResource(_resource.copyWith(error: null), notify: false); notifyListeners(); try { final page = await PaymentService.listPage( org.current.id, limit: _limit, cursor: cursor, sourceRef: _sourceRef, destinationRef: _destinationRef, states: _states, ); if (seq != _opSeq) return; final combined = List.from(payments)..addAll(page.items); _nextCursor = _normalize(page.nextCursor); _applyResource( _resource.copyWith(data: combined, error: null), notify: false, ); } catch (e) { if (seq != _opSeq) return; _applyResource( _resource.copyWith(error: toException(e)), notify: false, ); } finally { if (seq == _opSeq) { _isLoadingMore = false; notifyListeners(); } } } void reset() { _opSeq++; _isLoaded = false; _isLoadingMore = false; _nextCursor = null; _limit = null; _sourceRef = null; _destinationRef = null; _states = null; _resource = Resource(data: []); notifyListeners(); } void addPayments(List items, {bool prepend = true}) { if (items.isEmpty) return; final current = List.from(payments); final existingRefs = {}; for (final payment in current) { final ref = payment.paymentRef; if (ref != null && ref.isNotEmpty) { existingRefs.add(ref); } } final newItems = items.where((payment) { final ref = payment.paymentRef; if (ref == null || ref.isEmpty) return true; return !existingRefs.contains(ref); }).toList(); if (newItems.isEmpty) return; final combined = prepend ? [...newItems, ...current] : [...current, ...newItems]; _applyResource(_resource.copyWith(data: combined, error: null), notify: true); } void _applyResource(Resource> newResource, {required bool notify}) { _resource = newResource; if (notify) notifyListeners(); } String? _normalize(String? value) { final trimmed = value?.trim(); if (trimmed == null || trimmed.isEmpty) return null; return trimmed; } List? _normalizeStates(List? states) { if (states == null || states.isEmpty) return null; final normalized = states .map((state) => state.trim()) .where((state) => state.isNotEmpty) .toList(); if (normalized.isEmpty) return null; return normalized; } }