import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/api/api_client.dart'; import '../../core/cache/cached.dart'; import '../portfolio/providers.dart' show scopeProvider, scopesProvider; import 'data/rebalance_api.dart'; final rebalanceApiProvider = Provider( (ref) => RebalanceApi(ref.watch(apiProvider).dio), ); /// A portfolio the user can rebalance, derived from the scope list the analytics API /// already publishes (`portfolio:`): there is no separate portfolios endpoint, and /// inventing one would be a contract of its own. class PortfolioRef { const PortfolioRef(this.id, this.name); final int id; final String name; } final portfoliosProvider = FutureProvider.autoDispose>(( ref, ) async { final scopes = await ref.watch(scopesProvider.future); return [ for (final s in scopes) if (s.scope.startsWith('portfolio:')) PortfolioRef(int.parse(s.scope.substring('portfolio:'.length)), s.name), ]; }); /// The portfolio the rebalance screen is working on. Null until the list loads; seeded from /// the app-wide scope when that scope already names a portfolio. final selectedPortfolioProvider = StateProvider((ref) { final scope = ref.watch(scopeProvider); if (!scope.startsWith('portfolio:')) return null; return int.tryParse(scope.substring('portfolio:'.length)); }); final targetDimensionProvider = StateProvider((ref) => 'asset_class'); /// What-if cash for the recommendations, as a decimal string. Null = use the real balance. final whatIfCashProvider = StateProvider((ref) => null); /// See `docs/ai/offline-cache.md`. No portfolio selected yet is a live, empty answer — not a /// stale one — so it is wrapped with `fetchedAt: null` rather than left unwrapped. final targetsProvider = FutureProvider.autoDispose>(( ref, ) async { final id = ref.watch(selectedPortfolioProvider); final dimension = ref.watch(targetDimensionProvider); if (id == null) return Cached(TargetSet(dimension: dimension)); return ref.watch(rebalanceApiProvider).getTargets(id, dimension: dimension); }); final rebalancePlanProvider = FutureProvider.autoDispose>((ref) async { final id = ref.watch(selectedPortfolioProvider); final dimension = ref.watch(targetDimensionProvider); final cash = ref.watch(whatIfCashProvider); if (id == null) return const Cached(null); return ref .watch(rebalanceApiProvider) .plan(id, dimension: dimension, cashAvailable: cash); }); /// After a successful PUT the numbers are recomputed on the server, so both sides of the /// screen are refetched rather than patched in place. void invalidateRebalanceProviders(WidgetRef ref) { ref.invalidate(targetsProvider); ref.invalidate(rebalancePlanProvider); }