import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/api/api_client.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); final targetsProvider = FutureProvider.autoDispose((ref) async { final id = ref.watch(selectedPortfolioProvider); final dimension = ref.watch(targetDimensionProvider); if (id == null) return 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 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); }