import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:geolocator/geolocator.dart';
import 'package:go_router/go_router.dart';

import '../../../core/constants/app_colors.dart';
import '../../../core/constants/app_typography.dart';
import '../../../core/utils/category_mapper.dart';
import '../../../domain/entities/restaurant.dart';
import '../../../domain/entities/weather_info.dart';
import '../../providers/ad_provider.dart';
import '../../providers/location_provider.dart';
import '../../providers/recommendation_provider.dart';
import '../../providers/restaurant_provider.dart';
import '../../providers/settings_provider.dart';
import '../../providers/weather_provider.dart';
import 'widgets/recommendation_result_dialog.dart';

class RandomSelectionScreen extends ConsumerStatefulWidget {
  const RandomSelectionScreen({super.key});

  @override
  ConsumerState<RandomSelectionScreen> createState() =>
      _RandomSelectionScreenState();
}

class _RandomSelectionScreenState extends ConsumerState<RandomSelectionScreen> {
  double _distanceValue = 500;
  final List<String> _selectedCategories = [];
  final List<String> _excludedRestaurantIds = [];
  bool _isProcessingRecommendation = false;
  bool _hasUserAdjustedCategories = false;

  @override
  Widget build(BuildContext context) {
    final isDark = Theme.of(context).brightness == Brightness.dark;
    final locationAsync = ref.watch(currentLocationWithFallbackProvider);
    final restaurantsAsync = ref.watch(restaurantListProvider);
    final categoriesAsync = ref.watch(categoriesProvider);
    final screenshotModeEnabled = ref
        .watch(screenshotModeEnabledProvider)
        .maybeWhen(data: (value) => value, orElse: () => false);
    final readiness = _evaluateRecommendationReadiness(
      locationAsync: locationAsync,
      restaurantsAsync: restaurantsAsync,
      categoriesAsync: categoriesAsync,
      screenshotModeEnabled: screenshotModeEnabled,
    );

    return Scaffold(
      backgroundColor: isDark
          ? AppColors.darkBackground
          : AppColors.lightBackground,
      appBar: AppBar(
        title: const Text('오늘 뭐 먹Z?'),
        backgroundColor: isDark
            ? AppColors.darkPrimary
            : AppColors.lightPrimary,
        foregroundColor: Colors.white,
        elevation: 0,
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // 상단 요약 바 (높이 최소화)
            Card(
              color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
              elevation: 1,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(12),
              ),
              child: Padding(
                padding: const EdgeInsets.symmetric(
                  horizontal: 12,
                  vertical: 10,
                ),
                child: Row(
                  children: [
                    Container(
                      width: 36,
                      height: 36,
                      decoration: BoxDecoration(
                        color: AppColors.lightPrimary.withOpacity(0.1),
                        borderRadius: BorderRadius.circular(10),
                      ),
                      child: const Icon(
                        Icons.restaurant,
                        size: 20,
                        color: AppColors.lightPrimary,
                      ),
                    ),
                    const SizedBox(width: 10),
                    Expanded(
                      child: Consumer(
                        builder: (context, ref, child) {
                          final restaurantsAsync = ref.watch(
                            restaurantListProvider,
                          );
                          return restaurantsAsync.when(
                            data: (restaurants) => Text(
                              '등록된 맛집 ${restaurants.length}개',
                              style: AppTypography.heading2(
                                isDark,
                              ).copyWith(fontSize: 18),
                            ),
                            loading: () => const SizedBox(
                              height: 20,
                              width: 20,
                              child: CircularProgressIndicator(
                                strokeWidth: 2,
                                color: AppColors.lightPrimary,
                              ),
                            ),
                            error: (_, __) => Text(
                              '등록된 맛집 0개',
                              style: AppTypography.heading2(
                                isDark,
                              ).copyWith(fontSize: 18),
                            ),
                          );
                        },
                      ),
                    ),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 12),

            // 날씨 정보 카드
            Card(
              color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
              elevation: 1,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(12),
              ),
              child: Padding(
                padding: const EdgeInsets.symmetric(
                  horizontal: 12,
                  vertical: 14,
                ),
                child: Consumer(
                  builder: (context, ref, child) {
                    final weatherAsync = ref.watch(weatherProvider);
                    const double sectionHeight = 112;
                    return weatherAsync.when(
                      data: (weather) => SizedBox(
                        height: sectionHeight,
                        child: Row(
                          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                          children: [
                            _buildWeatherData('지금', weather.current, isDark),
                            Container(
                              width: 1,
                              height: 50,
                              color: isDark
                                  ? AppColors.darkDivider
                                  : AppColors.lightDivider,
                            ),
                            _buildWeatherData(
                              '1시간 후',
                              weather.nextHour,
                              isDark,
                            ),
                          ],
                        ),
                      ),
                      loading: () => const SizedBox(
                        height: sectionHeight,
                        child: Center(
                          child: CircularProgressIndicator(
                            color: AppColors.lightPrimary,
                          ),
                        ),
                      ),
                      error: (_, __) => SizedBox(
                        height: sectionHeight,
                        child: Column(
                          mainAxisAlignment: MainAxisAlignment.center,
                          children: [
                            const Icon(
                              Icons.cloud_off,
                              color: AppColors.lightWarning,
                              size: 28,
                            ),
                            const SizedBox(height: 8),
                            Text(
                              '날씨 정보를 불러오지 못했습니다.',
                              style: AppTypography.body2(isDark),
                              textAlign: TextAlign.center,
                            ),
                            const SizedBox(height: 4),
                            Text(
                              '네트워크/위치 권한을 확인한 뒤 다시 시도해 주세요.',
                              style: AppTypography.caption(isDark),
                              textAlign: TextAlign.center,
                            ),
                            const SizedBox(height: 8),
                            TextButton.icon(
                              onPressed: () => ref.refresh(weatherProvider),
                              icon: const Icon(Icons.refresh),
                              label: const Text('다시 시도'),
                            ),
                          ],
                        ),
                      ),
                    );
                  },
                ),
              ),
            ),

            const SizedBox(height: 12),

            // 카테고리 선택 카드
            Card(
              color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
              elevation: 1,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(12),
              ),
              child: Padding(
                padding: const EdgeInsets.fromLTRB(12, 14, 12, 12),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text('카테고리', style: AppTypography.heading2(isDark)),
                    const SizedBox(height: 10),
                    Consumer(
                      builder: (context, ref, child) {
                        final categoriesAsync = ref.watch(categoriesProvider);

                        return categoriesAsync.when(
                          data: (categories) {
                            _ensureDefaultCategorySelection(categories);
                            final selectedCategories =
                                _effectiveSelectedCategories(categories);
                            final isAllSelected = _isAllCategoriesSelected(
                              categories,
                            );
                            final categoryChips = categories
                                .map(
                                  (category) => _buildCategoryChip(
                                    category,
                                    isDark,
                                    selectedCategories.contains(category),
                                    categories,
                                  ),
                                )
                                .toList();
                            return Wrap(
                              spacing: 8,
                              runSpacing: 10,
                              children: categories.isEmpty
                                  ? [const Text('카테고리 없음')]
                                  : [
                                      _buildAllCategoryChip(
                                        isDark,
                                        isAllSelected,
                                        categories,
                                      ),
                                      ...categoryChips,
                                    ],
                            );
                          },
                          loading: () => const CircularProgressIndicator(),
                          error: (_, __) => const Text('카테고리를 불러올 수 없습니다'),
                        );
                      },
                    ),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 12),

            // 거리 설정 카드
            Card(
              color: isDark ? AppColors.darkSurface : AppColors.lightSurface,
              elevation: 1,
              shape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(12),
              ),
              child: Padding(
                padding: const EdgeInsets.fromLTRB(12, 14, 12, 12),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text('최대 거리', style: AppTypography.heading2(isDark)),
                    const SizedBox(height: 10),
                    Row(
                      children: [
                        Expanded(
                          child: SliderTheme(
                            data: SliderTheme.of(context).copyWith(
                              activeTrackColor: AppColors.lightPrimary,
                              inactiveTrackColor: AppColors.lightPrimary
                                  .withValues(alpha: 0.3),
                              thumbColor: AppColors.lightPrimary,
                              trackHeight: 4,
                            ),
                            child: Slider(
                              value: _distanceValue,
                              min: 100,
                              max: 2000,
                              divisions: 19,
                              onChanged: (value) {
                                setState(() => _distanceValue = value);
                              },
                            ),
                          ),
                        ),
                        const SizedBox(width: 12),
                        Text(
                          '${_distanceValue.toInt()}m',
                          style: AppTypography.body1(
                            isDark,
                          ).copyWith(fontWeight: FontWeight.bold),
                        ),
                      ],
                    ),
                    const SizedBox(height: 6),
                    Consumer(
                      builder: (context, ref, child) {
                        final locationAsync = ref.watch(
                          currentLocationWithFallbackProvider,
                        );
                        final restaurantsAsync = ref.watch(
                          restaurantListProvider,
                        );
                        final categoriesAsync = ref.watch(categoriesProvider);

                        final location = locationAsync.maybeWhen(
                          data: (pos) => pos,
                          orElse: () => null,
                        );
                        final restaurants = restaurantsAsync.maybeWhen(
                          data: (list) => list,
                          orElse: () => null,
                        );
                        final categories = categoriesAsync.maybeWhen(
                          data: (list) => list,
                          orElse: () => const <String>[],
                        );

                        if (location != null && restaurants != null) {
                          final count = _getRestaurantCountInRange(
                            restaurants,
                            location,
                            _distanceValue,
                            _effectiveSelectedCategories(categories),
                          );
                          return Text(
                            '$count개 맛집 포함',
                            style: AppTypography.caption(isDark),
                          );
                        }

                        return Text(
                          '위치 정보를 가져오는 중...',
                          style: AppTypography.caption(isDark),
                        );
                      },
                    ),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 16),

            // 추천받기 버튼
            ElevatedButton(
              onPressed: !_isProcessingRecommendation && readiness.canRecommend
                  ? () => _startRecommendation()
                  : null,
              style: ElevatedButton.styleFrom(
                backgroundColor: AppColors.lightPrimary,
                foregroundColor: Colors.white,
                padding: const EdgeInsets.symmetric(vertical: 20),
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(12),
                ),
                elevation: 3,
              ),
              child: _isProcessingRecommendation
                  ? const SizedBox(
                      height: 24,
                      width: 24,
                      child: CircularProgressIndicator(
                        strokeWidth: 2.5,
                        color: Colors.white,
                      ),
                    )
                  : const Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        Icon(Icons.play_arrow, size: 28),
                        SizedBox(width: 8),
                        Text(
                          '광고보고 추천받기',
                          style: TextStyle(
                            fontSize: 18,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ],
                    ),
            ),

            if (readiness.message != null)
              Padding(
                padding: const EdgeInsets.only(top: 8),
                child: Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Icon(
                      readiness.icon,
                      size: 18,
                      color: _readinessColor(readiness, isDark),
                    ),
                    const SizedBox(width: 8),
                    Expanded(
                      child: Text(
                        readiness.message!,
                        style: AppTypography.caption(
                          isDark,
                        ).copyWith(color: _readinessColor(readiness, isDark)),
                      ),
                    ),
                  ],
                ),
              ),

            const SizedBox(height: 16),
            // const NativeAdPlaceholder(
            //   margin: EdgeInsets.symmetric(vertical: 8),
            // ),
          ],
        ),
      ),
    );
  }

  Widget _buildWeatherData(String label, WeatherData weatherData, bool isDark) {
    return Column(
      children: [
        Text(label, style: AppTypography.caption(isDark)),
        const SizedBox(height: 8),
        Icon(
          weatherData.isRainy ? Icons.umbrella : Icons.wb_sunny,
          color: weatherData.isRainy ? Colors.blue : Colors.orange,
          size: 32,
        ),
        const SizedBox(height: 4),
        Text(
          '${weatherData.temperature}°C',
          style: AppTypography.body1(
            isDark,
          ).copyWith(fontWeight: FontWeight.bold),
        ),
        Text(weatherData.description, style: AppTypography.caption(isDark)),
      ],
    );
  }

  Widget _buildAllCategoryChip(
    bool isDark,
    bool isSelected,
    List<String> availableCategories,
  ) {
    const icon = Icons.restaurant_menu;
    return FilterChip(
      label: const Text('전체'),
      selected: isSelected,
      onSelected: (_) => _handleToggleAllCategories(availableCategories),
      avatar: _buildChipIcon(
        isSelected: isSelected,
        icon: icon,
        color: AppColors.lightPrimary,
      ),
      backgroundColor: isDark
          ? AppColors.darkSurface
          : AppColors.lightBackground,
      selectedColor: AppColors.lightPrimary.withValues(alpha: 0.2),
      checkmarkColor: AppColors.lightPrimary,
      showCheckmark: false,
      labelStyle: TextStyle(
        color: isSelected
            ? AppColors.lightPrimary
            : (isDark ? AppColors.darkTextPrimary : AppColors.lightTextPrimary),
        fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
      ),
      side: BorderSide(
        color: isSelected
            ? AppColors.lightPrimary
            : (isDark ? AppColors.darkDivider : AppColors.lightDivider),
      ),
    );
  }

  Widget _buildCategoryChip(
    String category,
    bool isDark,
    bool isSelected,
    List<String> availableCategories,
  ) {
    final icon = CategoryMapper.getIcon(category);
    final color = CategoryMapper.getColor(category);
    final activeColor = AppColors.lightPrimary;
    final accentColor = isSelected ? activeColor : color;
    final displayName = CategoryMapper.getDisplayName(category);
    return FilterChip(
      label: Text(displayName),
      selected: isSelected,
      onSelected: (selected) =>
          _handleCategoryToggle(category, selected, availableCategories),
      avatar: _buildChipIcon(
        isSelected: isSelected,
        icon: icon,
        color: accentColor,
      ),
      backgroundColor: isDark
          ? AppColors.darkSurface
          : AppColors.lightBackground,
      selectedColor: activeColor.withValues(alpha: 0.2),
      checkmarkColor: accentColor,
      showCheckmark: false,
      labelStyle: TextStyle(
        color: isSelected
            ? accentColor
            : (isDark ? AppColors.darkTextPrimary : AppColors.lightTextPrimary),
        fontWeight: isSelected ? FontWeight.w600 : FontWeight.w500,
      ),
      side: BorderSide(
        color: isSelected
            ? accentColor
            : (isDark ? AppColors.darkDivider : AppColors.lightDivider),
      ),
    );
  }

  void _handleCategoryToggle(
    String category,
    bool isSelected,
    List<String> availableCategories,
  ) {
    final currentSelection = _effectiveSelectedCategories(availableCategories);
    final nextSelection = currentSelection.toSet();

    if (isSelected) {
      nextSelection.add(category);
    } else {
      nextSelection.remove(category);
    }

    setState(() {
      _hasUserAdjustedCategories = true;
      _selectedCategories
        ..clear()
        ..addAll(nextSelection);
    });
  }

  void _handleToggleAllCategories(List<String> availableCategories) {
    if (availableCategories.isEmpty) return;

    final shouldSelectAll = !_isAllCategoriesSelected(availableCategories);

    setState(() {
      _hasUserAdjustedCategories = true;
      _selectedCategories
        ..clear()
        ..addAll(shouldSelectAll ? availableCategories : <String>[]);
    });
  }

  bool _isAllCategoriesSelected(List<String> availableCategories) {
    if (availableCategories.isEmpty) return false;
    if (!_hasUserAdjustedCategories) return true;

    final availableSet = availableCategories.toSet();
    final selectedSet = _selectedCategories.toSet();

    return selectedSet.isNotEmpty &&
        availableSet.difference(selectedSet).isEmpty;
  }

  Widget _buildChipIcon({
    required bool isSelected,
    required IconData icon,
    required Color color,
  }) {
    final displayedIcon = isSelected ? Icons.check : icon;
    return SizedBox(
      width: 20,
      height: 20,
      child: Icon(displayedIcon, size: 18, color: color),
    );
  }

  int _getRestaurantCountInRange(
    List<Restaurant> restaurants,
    Position location,
    double maxDistance,
    List<String> selectedCategories,
  ) {
    return restaurants.where((restaurant) {
      final distance = Geolocator.distanceBetween(
        location.latitude,
        location.longitude,
        restaurant.latitude,
        restaurant.longitude,
      );
      final isWithinDistance = distance <= maxDistance;
      final matchesCategory = selectedCategories.isEmpty
          ? true
          : selectedCategories.contains(restaurant.category);
      return isWithinDistance && matchesCategory;
    }).length;
  }

  _RecommendationReadiness _evaluateRecommendationReadiness({
    required AsyncValue<Position> locationAsync,
    required AsyncValue<List<Restaurant>> restaurantsAsync,
    required AsyncValue<List<String>> categoriesAsync,
    bool screenshotModeEnabled = false,
  }) {
    if (screenshotModeEnabled) {
      return const _RecommendationReadiness(canRecommend: true);
    }

    if (locationAsync.isLoading) {
      return const _RecommendationReadiness(
        canRecommend: false,
        message: '위치 정보를 불러오는 중입니다. 잠시만 기다려 주세요.',
        icon: Icons.location_searching,
        isWarning: true,
      );
    }

    if (locationAsync.hasError) {
      return const _RecommendationReadiness(
        canRecommend: false,
        message: '위치 권한 또는 설정을 확인해 주세요.',
        icon: Icons.gps_off,
        isError: true,
      );
    }

    final location = locationAsync.value;
    if (location == null) {
      return const _RecommendationReadiness(
        canRecommend: false,
        message: '위치 정보를 확인할 수 없습니다.',
        icon: Icons.location_disabled,
        isError: true,
      );
    }

    if (restaurantsAsync.isLoading) {
      return const _RecommendationReadiness(
        canRecommend: false,
        message: '맛집 목록을 불러오는 중입니다.',
        icon: Icons.restaurant_menu,
        isWarning: true,
      );
    }

    final restaurants = restaurantsAsync.value;
    if (restaurants == null || restaurants.isEmpty) {
      return const _RecommendationReadiness(
        canRecommend: false,
        message: '등록된 맛집이 없습니다. 맛집을 추가해 주세요.',
        icon: Icons.playlist_remove,
        isError: true,
      );
    }

    if (categoriesAsync.isLoading) {
      return const _RecommendationReadiness(
        canRecommend: false,
        message: '카테고리를 불러오는 중입니다.',
        icon: Icons.category_outlined,
        isWarning: true,
      );
    }

    final categories = categoriesAsync.value ?? const <String>[];
    final count = _getRestaurantCountInRange(
      restaurants,
      location,
      _distanceValue,
      _effectiveSelectedCategories(categories),
    );
    if (count == 0) {
      return const _RecommendationReadiness(
        canRecommend: false,
        message: '선택한 거리/카테고리에 해당하는 맛집이 없습니다. 범위를 넓혀 보세요.',
        icon: Icons.travel_explore,
        isWarning: true,
      );
    }

    final usesFallback = _isFallbackPosition(location);
    return _RecommendationReadiness(
      canRecommend: true,
      message: usesFallback
          ? '기본 위치(서울 시청) 기준으로 추천 중입니다. 위치 권한을 허용하면 더 정확해요.'
          : null,
      icon: usesFallback ? Icons.gps_not_fixed : Icons.check_circle,
      isWarning: usesFallback,
    );
  }

  bool _isFallbackPosition(Position position) {
    final fallback = defaultPosition();
    return position.latitude == fallback.latitude &&
        position.longitude == fallback.longitude;
  }

  Color _readinessColor(_RecommendationReadiness readiness, bool isDark) {
    if (readiness.isError) {
      return isDark ? AppColors.darkError : AppColors.lightError;
    }
    if (readiness.isWarning) {
      return isDark ? AppColors.darkWarning : AppColors.lightWarning;
    }
    return isDark ? AppColors.darkTextSecondary : AppColors.lightTextSecondary;
  }

  Future<void> _startRecommendation({
    bool skipAd = false,
    bool isReroll = false,
  }) async {
    if (_isProcessingRecommendation) return;

    if (!isReroll) {
      _excludedRestaurantIds.clear();
    }

    setState(() {
      _isProcessingRecommendation = true;
    });

    try {
      final candidate = await _generateRecommendationCandidate(
        excludedRestaurantIds: _excludedRestaurantIds,
      );
      if (candidate == null) {
        return;
      }
      final recommendedAt = DateTime.now();

      if (!skipAd) {
        final adService = ref.read(adServiceProvider);
        // Ad dialog 자체가 비동기 동작을 포함하므로 사용 후 mounted 체크를 수행한다.
        // ignore: use_build_context_synchronously
        final adWatched = await adService.showInterstitialAd(context);
        if (!mounted) return;
        if (!adWatched) {
          _showSnack('광고를 끝까지 시청해야 추천을 받을 수 있어요.', type: _SnackType.error);
          return;
        }
      }

      if (!mounted) return;
      await _showRecommendationDialog(candidate, recommendedAt: recommendedAt);
    } catch (_) {
      _showSnack('추천을 준비하는 중 문제가 발생했습니다.', type: _SnackType.error);
    } finally {
      if (mounted) {
        setState(() {
          _isProcessingRecommendation = false;
        });
      }
    }
  }

  Future<Restaurant?> _generateRecommendationCandidate({
    List<String> excludedRestaurantIds = const [],
  }) async {
    final notifier = ref.read(recommendationNotifierProvider.notifier);
    final categories = ref
        .read(categoriesProvider)
        .maybeWhen(data: (list) => list, orElse: () => const <String>[]);

    final recommendation = await notifier.getRandomRecommendation(
      maxDistance: _distanceValue,
      selectedCategories: _effectiveSelectedCategories(categories),
      excludedRestaurantIds: excludedRestaurantIds,
      shouldSaveRecord: false,
    );

    final result = ref.read(recommendationNotifierProvider);

    if (result.hasError) {
      final message = result.error?.toString() ?? '알 수 없는 오류';
      _showSnack('추천 중 오류가 발생했습니다: $message', type: _SnackType.error);
      return null;
    }

    if (recommendation == null) {
      _showSnack(
        '조건에 맞는 식당이 존재하지 않습니다. 광고는 재생되지 않았습니다.',
        type: _SnackType.warning,
      );
    }
    return recommendation;
  }

  Future<void> _showRecommendationDialog(
    Restaurant restaurant, {
    DateTime? recommendedAt,
  }) async {
    final location = ref
        .read(currentLocationWithFallbackProvider)
        .maybeWhen(data: (pos) => pos, orElse: () => null);

    final result = await showDialog<RecommendationDialogResult>(
      context: context,
      barrierDismissible: false,
      builder: (dialogContext) => RecommendationResultDialog(
        restaurant: restaurant,
        currentLatitude: location?.latitude,
        currentLongitude: location?.longitude,
      ),
    );

    if (!mounted) return;

    switch (result) {
      case RecommendationDialogResult.reroll:
        setState(() {
          _excludedRestaurantIds.add(restaurant.id);
        });
        await _startRecommendation(skipAd: false, isReroll: true);
        break;
      case RecommendationDialogResult.confirm:
      case RecommendationDialogResult.autoConfirm:
      default:
        await _handleRecommendationAccepted(
          restaurant,
          recommendedAt ?? DateTime.now(),
        );
        break;
    }
  }

  Future<void> _handleRecommendationAccepted(
    Restaurant restaurant,
    DateTime recommendationTime,
  ) async {
    try {
      final recommendationNotifier = ref.read(
        recommendationNotifierProvider.notifier,
      );
      await recommendationNotifier.saveRecommendationRecord(
        restaurant,
        recommendationTime: recommendationTime,
        visited: false,
      );
      _showSnack('맛있게 드세요! 🍴', type: _SnackType.success);
      if (mounted) {
        setState(() {
          _excludedRestaurantIds.clear();
        });
      }
    } catch (_) {
      _showSnack('방문 기록 또는 알림 예약에 실패했습니다.', type: _SnackType.error);
    }

    if (!mounted) return;
    final ts = DateTime.now().millisecondsSinceEpoch;
    context.go('/home?tab=calendar&ts=$ts');
  }

  void _showSnack(String message, {_SnackType type = _SnackType.info}) {
    if (!mounted) return;
    final bgColor = switch (type) {
      _SnackType.success => Colors.teal.shade600,
      _SnackType.warning => Colors.orange.shade600,
      _SnackType.error => AppColors.lightError,
      _SnackType.info => Colors.blueGrey.shade600,
    };
    final topInset = MediaQuery.of(context).viewPadding.top;
    ScaffoldMessenger.of(context)
      ..hideCurrentSnackBar()
      ..showSnackBar(
        SnackBar(
          content: Text(message),
          backgroundColor: bgColor,
          behavior: SnackBarBehavior.floating,
          margin: EdgeInsets.fromLTRB(
            16,
            (topInset > 0 ? topInset : 16) + 8,
            16,
            0,
          ),
          duration: const Duration(seconds: 3),
        ),
      );
  }

  void _ensureDefaultCategorySelection(List<String> categories) {
    if (_hasUserAdjustedCategories || categories.isEmpty) return;

    final current = _selectedCategories.toSet();
    final incoming = categories.toSet();

    if (current.length == incoming.length && current.containsAll(incoming)) {
      return;
    }

    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (!mounted) return;
      setState(() {
        _selectedCategories
          ..clear()
          ..addAll(categories);
      });
    });
  }

  List<String> _effectiveSelectedCategories(List<String> availableCategories) {
    final availableSet = availableCategories.toSet();
    final filtered = _selectedCategories
        .where((category) => availableSet.contains(category))
        .toList();

    if (!_hasUserAdjustedCategories) {
      return availableCategories;
    }

    return filtered;
  }
}

class _RecommendationReadiness {
  final bool canRecommend;
  final String? message;
  final IconData icon;
  final bool isWarning;
  final bool isError;

  const _RecommendationReadiness({
    required this.canRecommend,
    this.message,
    this.icon = Icons.info_outline,
    this.isWarning = false,
    this.isError = false,
  });
}

enum _SnackType { info, warning, error, success }
