import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:lunchpick/domain/entities/visit_record.dart'; import 'package:lunchpick/domain/repositories/visit_repository.dart'; import 'package:lunchpick/presentation/providers/di_providers.dart'; import 'package:lunchpick/presentation/providers/restaurant_provider.dart'; import 'package:uuid/uuid.dart'; /// 방문 기록 목록 Provider final visitRecordsProvider = StreamProvider>((ref) { final repository = ref.watch(visitRepositoryProvider); return repository.watchVisitRecords(); }); /// 날짜별 방문 기록 Provider final visitRecordsByDateProvider = FutureProvider.family, DateTime>((ref, date) async { final repository = ref.watch(visitRepositoryProvider); return repository.getVisitRecordsByDate(date); }); /// 맛집별 방문 기록 Provider final visitRecordsByRestaurantProvider = FutureProvider.family, String>((ref, restaurantId) async { final repository = ref.watch(visitRepositoryProvider); return repository.getVisitRecordsByRestaurantId(restaurantId); }); /// 월별 방문 통계 Provider final monthlyVisitStatsProvider = FutureProvider.family, ({int year, int month})>(( ref, params, ) async { final repository = ref.watch(visitRepositoryProvider); return repository.getMonthlyVisitStats(params.year, params.month); }); /// 방문 기록 관리 StateNotifier class VisitNotifier extends StateNotifier> { final VisitRepository _repository; final Ref _ref; VisitNotifier(this._repository, this._ref) : super(const AsyncValue.data(null)); /// 방문 기록 추가 Future addVisitRecord({ required String restaurantId, required DateTime visitDate, bool isConfirmed = false, }) async { state = const AsyncValue.loading(); try { final visitRecord = VisitRecord( id: const Uuid().v4(), restaurantId: restaurantId, visitDate: visitDate, isConfirmed: isConfirmed, createdAt: DateTime.now(), ); await _repository.addVisitRecord(visitRecord); // 맛집의 마지막 방문일도 업데이트 final restaurantNotifier = _ref.read(restaurantNotifierProvider.notifier); await restaurantNotifier.updateLastVisitDate(restaurantId, visitDate); state = const AsyncValue.data(null); } catch (e, stack) { state = AsyncValue.error(e, stack); } } /// 방문 확인 Future confirmVisit(String visitRecordId) async { state = const AsyncValue.loading(); try { await _repository.confirmVisit(visitRecordId); state = const AsyncValue.data(null); } catch (e, stack) { state = AsyncValue.error(e, stack); } } /// 방문 기록 삭제 Future deleteVisitRecord(String id) async { state = const AsyncValue.loading(); try { await _repository.deleteVisitRecord(id); state = const AsyncValue.data(null); } catch (e, stack) { state = AsyncValue.error(e, stack); } } /// 추천 후 자동 방문 기록 생성 Future createVisitFromRecommendation({ required String restaurantId, required DateTime recommendationTime, bool isConfirmed = false, }) async { // 추천 시간으로부터 1.5시간 후를 방문 시간으로 설정 final visitTime = recommendationTime.add(const Duration(minutes: 90)); await addVisitRecord( restaurantId: restaurantId, visitDate: visitTime, isConfirmed: isConfirmed, ); } } /// VisitNotifier Provider final visitNotifierProvider = StateNotifierProvider>((ref) { final repository = ref.watch(visitRepositoryProvider); return VisitNotifier(repository, ref); }); /// 특정 맛집의 마지막 방문일 Provider final lastVisitDateProvider = FutureProvider.family(( ref, restaurantId, ) async { final repository = ref.watch(visitRepositoryProvider); return repository.getLastVisitDate(restaurantId); }); /// 기간별 방문 기록 Provider final visitRecordsByPeriodProvider = FutureProvider.family< List, ({DateTime startDate, DateTime endDate}) >((ref, params) async { final allRecords = await ref.watch(visitRecordsProvider.future); return allRecords.where((record) { return record.visitDate.isAfter(params.startDate) && record.visitDate.isBefore( params.endDate.add(const Duration(days: 1)), ); }).toList()..sort((a, b) => b.visitDate.compareTo(a.visitDate)); }); /// 주간 방문 통계 Provider (최근 7일) final weeklyVisitStatsProvider = FutureProvider>((ref) async { final now = DateTime.now(); final startOfWeek = DateTime( now.year, now.month, now.day, ).subtract(const Duration(days: 6)); final records = await ref.watch( visitRecordsByPeriodProvider((startDate: startOfWeek, endDate: now)).future, ); final stats = {}; for (var i = 0; i < 7; i++) { final date = startOfWeek.add(Duration(days: i)); final dateKey = '${date.month}/${date.day}'; stats[dateKey] = records .where( (r) => r.visitDate.year == date.year && r.visitDate.month == date.month && r.visitDate.day == date.day, ) .length; } return stats; }); /// 자주 방문하는 맛집 Provider (상위 10개) final frequentRestaurantsProvider = FutureProvider>((ref) async { final allRecords = await ref.watch(visitRecordsProvider.future); final visitCounts = {}; for (final record in allRecords) { visitCounts[record.restaurantId] = (visitCounts[record.restaurantId] ?? 0) + 1; } final sorted = visitCounts.entries.toList() ..sort((a, b) => b.value.compareTo(a.value)); return sorted .take(10) .map((e) => (restaurantId: e.key, visitCount: e.value)) .toList(); }); /// 방문 기록 정렬 옵션 enum VisitSortOption { dateDesc, // 최신순 dateAsc, // 오래된순 restaurant, // 맛집별 } /// 정렬된 방문 기록 Provider final sortedVisitRecordsProvider = Provider.family>, VisitSortOption>(( ref, sortOption, ) { final recordsAsync = ref.watch(visitRecordsProvider); return recordsAsync.when( data: (records) { final sorted = List.from(records); switch (sortOption) { case VisitSortOption.dateDesc: sorted.sort((a, b) => b.visitDate.compareTo(a.visitDate)); break; case VisitSortOption.dateAsc: sorted.sort((a, b) => a.visitDate.compareTo(b.visitDate)); break; case VisitSortOption.restaurant: sorted.sort((a, b) => a.restaurantId.compareTo(b.restaurantId)); break; } return AsyncValue.data(sorted); }, loading: () => const AsyncValue.loading(), error: (error, stack) => AsyncValue.error(error, stack), ); }); /// 카테고리별 방문 통계 Provider final categoryVisitStatsProvider = FutureProvider>(( ref, ) async { final allRecords = await ref.watch(visitRecordsProvider.future); final restaurantsAsync = await ref.watch(restaurantListProvider.future); final categoryCount = {}; for (final record in allRecords) { final restaurant = restaurantsAsync .where((r) => r.id == record.restaurantId) .firstOrNull; if (restaurant != null) { categoryCount[restaurant.category] = (categoryCount[restaurant.category] ?? 0) + 1; } } return categoryCount; });