import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:hive_flutter/hive_flutter.dart'; import '../models/sake_item.dart'; // SakeItem exports ItemType import 'filter_providers.dart'; import 'theme_provider.dart'; // 1. Raw List Stream final rawSakeListItemsProvider = StreamProvider>((ref) { final box = Hive.box('sake_items'); // Use startWith to emit current value immediately return box.watch().map((event) => box.values.toList()).startWith(box.values.toList()); }); // 2. Phase D6: Mode-based Filtered Provider (セット商品・Draft除外) // 個人モード: セット商品とDraftを除外 // ビジネスモード: 全表示 final filteredByModeProvider = Provider>>((ref) { final rawListAsync = ref.watch(rawSakeListItemsProvider); // Performance optimization: Only watch isBusinessMode field to avoid unnecessary rebuilds final isBusinessMode = ref.watch(userProfileProvider.select((p) => p.isBusinessMode)); return rawListAsync.when( data: (rawList) { if (rawList.isEmpty) return const AsyncValue.data([]); // ビジネスモード: 全アイテム表示(セット商品含む) if (isBusinessMode) { return AsyncValue.data(rawList); } // 個人モード: セット商品とDraft(未解析)を除外 final filtered = rawList.where((item) => item.itemType == ItemType.sake && // セット商品除外 !item.isPendingAnalysis // Draft除外 ).toList(); return AsyncValue.data(filtered); }, loading: () => const AsyncValue.loading(), error: (e, s) => AsyncValue.error(e, s), ); }); // 3. Sort Order Stream final sakeSortOrderProvider = StreamProvider>((ref) { final box = Hive.box('settings'); return box.watch(key: 'sake_sort_order').map((event) { final List? stored = box.get('sake_sort_order'); return stored?.cast() ?? []; }).startWith(box.get('sake_sort_order')?.cast() ?? []); }); // Sort Mode Enum enum SortMode { newest, oldest, name, custom } class SakeSortModeNotifier extends Notifier { @override SortMode build() => SortMode.newest; void set(SortMode mode) => state = mode; } final sakeSortModeProvider = NotifierProvider(SakeSortModeNotifier.new); // 3. Combined Sorted List final sakeListProvider = Provider>>((ref) { final rawListAsync = ref.watch(filteredByModeProvider); final sortOrderAsync = ref.watch(sakeSortOrderProvider); final sortMode = ref.watch(sakeSortModeProvider); // Watch Filters final searchQuery = ref.watch(sakeSearchQueryProvider).toLowerCase(); final showFavoritesOnly = ref.watch(sakeFilterFavoriteProvider); final filterTag = ref.watch(sakeFilterTagProvider); final filterPrefecture = ref.watch(sakeFilterPrefectureProvider); return rawListAsync.when( data: (rawList) { if (rawList.isEmpty) return const AsyncValue.data([]); // 1. First, apply filters to raw list var filtered = rawList.where((item) { // Search Filter if (searchQuery.isNotEmpty) { final matches = item.displayData.displayName.toLowerCase().contains(searchQuery) || item.displayData.displayBrewery.toLowerCase().contains(searchQuery) || item.displayData.displayPrefecture.toLowerCase().contains(searchQuery); if (!matches) return false; } // Favorite Filter if (showFavoritesOnly) { if (!item.userData.isFavorite) return false; } // Prefecture Filter if (filterPrefecture != null) { if (item.displayData.displayPrefecture != filterPrefecture) return false; } // Tag Filter if (filterTag != null && filterTag != 'All') { // Special case for 'Set' which effectively filters by itemType if we had it, or implicit tag if (filterTag == 'Set') { // Assuming 'Set' tag is manually added or we check itemType if implemented // For now, check flavorTags for 'Set' or 'セット' final isSet = item.hiddenSpecs.flavorTags.contains('セット') || item.hiddenSpecs.flavorTags.contains('Set') || item.displayData.displayName.contains('セット'); if (!isSet) return false; } else { final matchesTag = item.hiddenSpecs.flavorTags.contains(filterTag); if (!matchesTag) return false; } } return true; }).toList(); // 2. Then apply sort based on SortMode switch (sortMode) { case SortMode.newest: filtered.sort((a, b) => b.metadata.createdAt.compareTo(a.metadata.createdAt)); break; case SortMode.oldest: filtered.sort((a, b) => a.metadata.createdAt.compareTo(b.metadata.createdAt)); break; case SortMode.name: filtered.sort((a, b) => a.displayData.displayName.compareTo(b.displayData.displayName)); break; case SortMode.custom: // Use Manual Sort Order return sortOrderAsync.when( data: (sortOrder) { if (sortOrder.isEmpty) { // Default fallback if no custom order filtered.sort((a, b) => b.metadata.createdAt.compareTo(a.metadata.createdAt)); return AsyncValue.data(filtered); } final orderMap = {for (var i = 0; i < sortOrder.length; i++) sortOrder[i]: i}; filtered.sort((a, b) { final idxA = orderMap[a.id]; final idxB = orderMap[b.id]; if (idxA != null && idxB != null) { return idxA.compareTo(idxB); } else if (idxA != null) { return -1; } else if (idxB != null) { return 1; } else { return b.metadata.createdAt.compareTo(a.metadata.createdAt); } }); return AsyncValue.data(filtered); }, loading: () => AsyncValue.data(filtered), error: (e, s) => AsyncValue.error(e, s), ); } return AsyncValue.data(filtered); }, loading: () => const AsyncValue.loading(), error: (e, s) => AsyncValue.error(e, s), ); }); // 4. Controller for Updating Order class SakeOrderController extends Notifier { @override void build() {} void updateOrder(List newOrder) { final box = Hive.box('settings'); final ids = newOrder.map((e) => e.id).toList(); box.put('sake_sort_order', ids); } } final sakeOrderControllerProvider = NotifierProvider(SakeOrderController.new); // 5. All Items Provider (ユーザーフィルタなし、モード別フィルタ適用済み) // ソムリエ診断など、ユーザーのフィルタリング操作を無視して全体の傾向を見たい場合に使用 // Phase D6: filteredByModeProviderを使用(個人モードではセット・Draft除外) final allSakeItemsProvider = Provider>>((ref) { final filteredAsync = ref.watch(filteredByModeProvider); return filteredAsync.when( data: (filteredList) { if (filteredList.isEmpty) return const AsyncValue.data([]); // ソートのみ適用(新しい順) final sorted = List.from(filteredList); sorted.sort((a, b) => b.metadata.createdAt.compareTo(a.metadata.createdAt)); return AsyncValue.data(sorted); }, loading: () => const AsyncValue.loading(), error: (e, s) => AsyncValue.error(e, s), ); }); extension StreamStartWith on Stream { Stream startWith(T initial) async* { yield initial; yield* this; } }