import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../shared/models/account.dart'; import '../../shared/models/transaction.dart'; import '../dashboard/provider.dart'; import '../settings/provider.dart'; enum StatsTimeFilter { allTime, month } final statsTimeFilterProvider = NotifierProvider<_StatsTimeFilterNotifier, StatsTimeFilter>( _StatsTimeFilterNotifier.new, ); class _StatsTimeFilterNotifier extends Notifier { @override StatsTimeFilter build() => StatsTimeFilter.month; void set(StatsTimeFilter v) => state = v; } class StatsSummary { final double income; final double expense; final double balance; final int transactionCount; final double averageIncome; final double averageExpense; const StatsSummary({ required this.income, required this.expense, required this.balance, required this.transactionCount, required this.averageIncome, required this.averageExpense, }); } String _resolveTargetCurrency( int activeIndex, List accounts, String globalCurrency, ) { if (activeIndex > 0 && activeIndex <= accounts.length) { return accounts[activeIndex - 1].currency; } return globalCurrency; } List _filterScopedTransactions( List txs, StatsTimeFilter timeFilter, ) { var filtered = txs.where((t) => t.category != 'Transfer'); if (timeFilter == StatsTimeFilter.month) { final now = DateTime.now(); filtered = filtered.where( (t) => t.date.year == now.year && t.date.month == now.month, ); } return filtered.toList(); } final statsCurrencyProvider = Provider((ref) { final index = ref.watch(activeAccountIndexProvider); final accountsAsync = ref.watch(accountsProvider); final globalCurrency = ref.watch(currencyProvider).code; final accounts = accountsAsync.value ?? []; final code = _resolveTargetCurrency(index, accounts, globalCurrency); return CurrencyInfo(currencyMap[code]?.symbol ?? '\$', code); }); final statsScopedTransactionsProvider = Provider>((ref) { final txs = ref.watch(accountFilteredTransactionsProvider); final timeFilter = ref.watch(statsTimeFilterProvider); return _filterScopedTransactions(txs, timeFilter); }); final statsIncomeTotalProvider = Provider((ref) { final exchange = ref.watch(exchangeRateServiceProvider); final index = ref.watch(activeAccountIndexProvider); final accountsAsync = ref.watch(accountsProvider); final globalCurrency = ref.watch(currencyProvider).code; final accounts = accountsAsync.value ?? []; final target = _resolveTargetCurrency(index, accounts, globalCurrency); return ref .watch(statsScopedTransactionsProvider) .where((t) => t.type == TransactionType.income) .fold(0.0, (sum, t) => sum + exchange.convert(t.amount, t.currencyCode, target)); }); final statsExpenseTotalProvider = Provider((ref) { final exchange = ref.watch(exchangeRateServiceProvider); final index = ref.watch(activeAccountIndexProvider); final accountsAsync = ref.watch(accountsProvider); final globalCurrency = ref.watch(currencyProvider).code; final accounts = accountsAsync.value ?? []; final target = _resolveTargetCurrency(index, accounts, globalCurrency); return ref .watch(statsScopedTransactionsProvider) .where((t) => t.type == TransactionType.expense) .fold(0.0, (sum, t) => sum + exchange.convert(t.amount, t.currencyCode, target)); }); final statsSummaryProvider = Provider((ref) { final exchange = ref.watch(exchangeRateServiceProvider); final index = ref.watch(activeAccountIndexProvider); final accountsAsync = ref.watch(accountsProvider); final globalCurrency = ref.watch(currencyProvider).code; final accounts = accountsAsync.value ?? []; final target = _resolveTargetCurrency(index, accounts, globalCurrency); final transactions = ref.watch(statsScopedTransactionsProvider); var income = 0.0; var expense = 0.0; var incomeCount = 0; var expenseCount = 0; for (final transaction in transactions) { final amount = exchange.convert(transaction.amount, transaction.currencyCode, target); if (transaction.type == TransactionType.income) { income += amount; incomeCount++; } if (transaction.type == TransactionType.expense) { expense += amount; expenseCount++; } } return StatsSummary( income: income, expense: expense, balance: income - expense, transactionCount: transactions.length, averageIncome: incomeCount == 0 ? 0 : income / incomeCount, averageExpense: expenseCount == 0 ? 0 : expense / expenseCount, ); }); final categoryExpenseProvider = Provider>((ref) { final exchange = ref.watch(exchangeRateServiceProvider); final index = ref.watch(activeAccountIndexProvider); final accountsAsync = ref.watch(accountsProvider); final globalCurrency = ref.watch(currencyProvider).code; final accounts = accountsAsync.value ?? []; final target = _resolveTargetCurrency(index, accounts, globalCurrency); final map = {}; for (final t in ref.watch(statsScopedTransactionsProvider)) { if (t.type != TransactionType.expense) continue; map[t.category] = (map[t.category] ?? 0) + exchange.convert(t.amount, t.currencyCode, target); } return map; }); final categoryIncomeProvider = Provider>((ref) { final exchange = ref.watch(exchangeRateServiceProvider); final index = ref.watch(activeAccountIndexProvider); final accountsAsync = ref.watch(accountsProvider); final globalCurrency = ref.watch(currencyProvider).code; final accounts = accountsAsync.value ?? []; final target = _resolveTargetCurrency(index, accounts, globalCurrency); final map = {}; for (final t in ref.watch(statsScopedTransactionsProvider)) { if (t.type != TransactionType.income) continue; map[t.category] = (map[t.category] ?? 0) + exchange.convert(t.amount, t.currencyCode, target); } return map; }); final monthlyBreakdownProvider = Provider>((ref) { final txs = ref.watch(accountFilteredTransactionsProvider); final exchange = ref.watch(exchangeRateServiceProvider); final index = ref.watch(activeAccountIndexProvider); final accountsAsync = ref.watch(accountsProvider); final globalCurrency = ref.watch(currencyProvider).code; final accounts = accountsAsync.value ?? []; final target = _resolveTargetCurrency(index, accounts, globalCurrency); final now = DateTime.now(); final months = []; for (var i = 5; i >= 0; i--) { final month = DateTime(now.year, now.month - i, 1); final total = txs .where( (t) => t.type == TransactionType.expense && t.category != 'Transfer' && t.date.year == month.year && t.date.month == month.month, ) .fold(0.0, (sum, t) { return sum + exchange.convert(t.amount, t.currencyCode, target); }); months.add(MonthlyData(month: month, amount: total)); } return months; }); final monthlyIncomeBreakdownProvider = Provider>((ref) { final txs = ref.watch(accountFilteredTransactionsProvider); final exchange = ref.watch(exchangeRateServiceProvider); final index = ref.watch(activeAccountIndexProvider); final accountsAsync = ref.watch(accountsProvider); final globalCurrency = ref.watch(currencyProvider).code; final accounts = accountsAsync.value ?? []; final target = _resolveTargetCurrency(index, accounts, globalCurrency); final now = DateTime.now(); final months = []; for (var i = 5; i >= 0; i--) { final month = DateTime(now.year, now.month - i, 1); final total = txs .where( (t) => t.type == TransactionType.income && t.category != 'Transfer' && t.date.year == month.year && t.date.month == month.month, ) .fold(0.0, (sum, t) { return sum + exchange.convert(t.amount, t.currencyCode, target); }); months.add(MonthlyData(month: month, amount: total)); } return months; }); class MonthlyData { final DateTime month; final double amount; MonthlyData({required this.month, required this.amount}); }