// ignore_for_file: unreachable_from_main

import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'provider_examples.g.dart';

/// ============================================================================
/// RIVERPOD 3.0 PROVIDER EXAMPLES WITH CODE GENERATION
/// ============================================================================
/// This file contains comprehensive examples of Riverpod 3.0 patterns
/// using the @riverpod annotation and code generation.
///
/// Key Changes in Riverpod 3.0:
/// - Unified Ref type (no more FutureProviderRef, StreamProviderRef, etc.)
/// - Simplified Notifier classes (no more separate AutoDisposeNotifier)
/// - Automatic retry for failed providers
/// - ref.mounted check after async operations
/// - Improved family parameters (just function parameters!)
/// ============================================================================

// ============================================================================
// 1. SIMPLE IMMUTABLE VALUE PROVIDER
// ============================================================================

/// Simple provider that returns an immutable value
/// Use this for constants, configurations, or computed values
@riverpod
String appVersion(Ref ref) {
  return '1.0.0';
}

/// Provider with computation
@riverpod
int pointsMultiplier(Ref ref) {
  // Can read other providers
  final userTier = 'diamond'; // This would come from another provider
  return userTier == 'diamond'
      ? 3
      : userTier == 'platinum'
      ? 2
      : 1;
}

// ============================================================================
// 2. ASYNC DATA FETCHING (FutureProvider pattern)
// ============================================================================

/// Async provider for fetching data once
/// Automatically handles loading and error states via AsyncValue
@riverpod
Future<String> userData(Ref ref) async {
  // Simulate API call
  await Future<void>.delayed(const Duration(seconds: 1));
  return 'User Data';
}

/// Async provider with parameters (Family pattern)
/// Parameters are just function parameters - much simpler than before!
@riverpod
Future<String> userProfile(Ref ref, String userId) async {
  // Simulate API call with userId
  await Future<void>.delayed(const Duration(seconds: 1));
  return 'Profile for user: $userId';
}

/// Async provider with multiple parameters
/// Named parameters, optional parameters, defaults - all supported!
@riverpod
Future<List<String>> productList(
  Ref ref, {
  required String category,
  int page = 1,
  int limit = 20,
  String? searchQuery,
}) async {
  // Simulate API call with parameters
  await Future<void>.delayed(const Duration(milliseconds: 500));
  return ['Product 1', 'Product 2', 'Product 3'];
}

// ============================================================================
// 3. STREAM PROVIDER
// ============================================================================

/// Stream provider for real-time data
/// Use this for WebSocket connections, real-time updates, etc.
@riverpod
Stream<int> timer(Ref ref) {
  return Stream.periodic(const Duration(seconds: 1), (count) => count);
}

/// Stream provider with parameters
@riverpod
Stream<String> chatMessages(Ref ref, String roomId) {
  // Simulate WebSocket stream
  return Stream.periodic(
    const Duration(seconds: 2),
    (count) => 'Message $count in room $roomId',
  );
}

// ============================================================================
// 4. NOTIFIER - MUTABLE STATE WITH METHODS
// ============================================================================

/// Notifier for mutable state with methods
/// Use this when you need to expose methods to modify state
///
/// The @riverpod annotation generates:
/// - counterProvider: Access the state
/// - counterProvider.notifier: Access the notifier methods
@riverpod
class Counter extends _$Counter {
  /// Build method returns the initial state
  @override
  int build() {
    return 0;
  }

  /// Methods to modify state
  void increment() {
    state++;
  }

  void decrement() {
    state--;
  }

  void reset() {
    state = 0;
  }

  void add(int value) {
    state += value;
  }
}

/// Notifier with parameters (Family pattern)
@riverpod
class CartQuantity extends _$CartQuantity {
  /// Parameters become properties you can access
  @override
  int build(String productId) {
    // Initialize with 0 or load from local storage
    return 0;
  }

  void increment() {
    state++;
  }

  void decrement() {
    if (state > 0) {
      state--;
    }
  }

  void setQuantity(int quantity) {
    state = quantity.clamp(0, 99);
  }
}

// ============================================================================
// 5. ASYNC NOTIFIER - MUTABLE STATE WITH ASYNC INITIALIZATION
// ============================================================================

/// AsyncNotifier for state that requires async initialization
/// Perfect for fetching data that can then be modified
///
/// State type: AsyncValue<UserProfile>
/// - AsyncValue.data(profile) when loaded
/// - AsyncValue.loading() when loading
/// - AsyncValue.error(error, stack) when error
@riverpod
class UserProfileNotifier extends _$UserProfileNotifier {
  /// Build method returns Future of the initial state
  @override
  Future<UserProfileData> build() async {
    // Fetch initial data
    await Future<void>.delayed(const Duration(seconds: 1));
    return UserProfileData(name: 'John Doe', email: 'john@example.com');
  }

  /// Method to update profile
  /// Uses AsyncValue.guard() for proper error handling
  Future<void> updateName(String name) async {
    // Set loading state
    state = const AsyncValue.loading();

    // Update state with error handling
    state = await AsyncValue.guard(() async {
      await Future.delayed(const Duration(milliseconds: 500));

      final currentProfile = await future; // Get current data
      return currentProfile.copyWith(name: name);
    });
  }

  /// Refresh data
  Future<void> refresh() async {
    state = const AsyncValue.loading();

    state = await AsyncValue.guard(() async {
      await Future.delayed(const Duration(seconds: 1));
      return UserProfileData(name: 'Refreshed', email: 'refresh@example.com');
    });
  }

  /// Method with ref.mounted check (Riverpod 3.0 feature)
  Future<void> updateWithMountedCheck(String name) async {
    await Future.delayed(const Duration(seconds: 2));

    // Check if provider is still mounted after async operation
    if (!ref.mounted) return;

    state = AsyncValue.data(
      UserProfileData(name: name, email: 'email@example.com'),
    );
  }
}

// Simple data class for example
class UserProfileData {
  UserProfileData({required this.name, required this.email});
  final String name;
  final String email;

  UserProfileData copyWith({String? name, String? email}) {
    return UserProfileData(name: name ?? this.name, email: email ?? this.email);
  }
}

// ============================================================================
// 6. STREAM NOTIFIER - MUTABLE STATE FROM STREAM
// ============================================================================

/// StreamNotifier for state that comes from a stream but can be modified
/// Use for WebSocket connections with additional actions
@riverpod
class LiveChatNotifier extends _$LiveChatNotifier {
  @override
  Stream<List<String>> build() {
    // Return the stream
    return Stream.periodic(
      const Duration(seconds: 1),
      (count) => ['Message $count'],
    );
  }

  /// Send a new message
  Future<void> sendMessage(String message) async {
    // Send via WebSocket/API
    await Future.delayed(const Duration(milliseconds: 100));
  }
}

// ============================================================================
// 7. PROVIDER DEPENDENCIES (COMPOSITION)
// ============================================================================

/// Provider that depends on other providers
@riverpod
Future<String> dashboardData(Ref ref) async {
  // Watch other providers
  final userData = await ref.watch(userDataProvider.future);
  final version = ref.watch(appVersionProvider);

  return 'Dashboard for $userData on version $version';
}

/// Provider that selectively watches for optimization
/// Note: userProfileProvider is actually a Family provider (takes userId parameter)
/// In real code, you would use the generated AsyncNotifier provider
@riverpod
String userDisplayName(Ref ref) {
  // Example: Watch a simple computed value based on other providers
  final version = ref.watch(appVersionProvider);

  // In a real app, you would watch the UserProfileNotifier like:
  // final asyncProfile = ref.watch(userProfileNotifierProvider);
  // return asyncProfile.when(...)

  return 'User on version $version';
}

// ============================================================================
// 8. KEEP ALIVE vs AUTO DISPOSE
// ============================================================================

/// By default, generated providers are autoDispose
/// They clean up when no longer used
@riverpod
String autoDisposeExample(Ref ref) {
  // This provider will be disposed when no longer watched
  ref.onDispose(() {
    // Clean up resources
  });

  return 'Auto disposed';
}

/// Keep alive provider - never auto-disposes
/// Use for global state, singletons, services
@Riverpod(keepAlive: true)
String keepAliveExample(Ref ref) {
  // This provider stays alive until the app closes
  return 'Kept alive';
}

// ============================================================================
// 9. LIFECYCLE HOOKS
// ============================================================================

/// Provider with lifecycle hooks
@riverpod
String lifecycleExample(Ref ref) {
  // Called when provider is first created
  ref.onDispose(() {
    // Clean up when provider is disposed
    print('Provider disposed');
  });

  ref.onCancel(() {
    // Called when last listener is removed (before dispose)
    print('Last listener removed');
  });

  ref.onResume(() {
    // Called when a new listener is added after onCancel
    print('New listener added');
  });

  return 'Lifecycle example';
}

// ============================================================================
// 10. INVALIDATION AND REFRESH
// ============================================================================

/// Provider showing how to invalidate and refresh
@riverpod
class DataManager extends _$DataManager {
  @override
  Future<String> build() async {
    await Future.delayed(const Duration(seconds: 1));
    return 'Initial data';
  }

  /// Refresh this provider's data
  Future<void> refresh() async {
    // Method 1: Use ref.invalidateSelf()
    ref.invalidateSelf();

    // Method 2: Manually update state
    state = const AsyncValue.loading();
    state = await AsyncValue.guard(() async {
      await Future.delayed(const Duration(seconds: 1));
      return 'Refreshed data';
    });
  }

  /// Invalidate another provider
  void invalidateOther() {
    ref.invalidate(userDataProvider);
  }
}

// ============================================================================
// 11. ERROR HANDLING PATTERNS
// ============================================================================

/// Provider with comprehensive error handling
@riverpod
class ErrorHandlingExample extends _$ErrorHandlingExample {
  @override
  Future<String> build() async {
    return await _fetchData();
  }

  Future<String> _fetchData() async {
    try {
      // Simulate API call
      await Future.delayed(const Duration(seconds: 1));
      return 'Success';
    } catch (e) {
      // Errors are automatically caught by AsyncValue
      rethrow;
    }
  }

  /// Update with error handling
  Future<void> updateData(String newData) async {
    state = const AsyncValue.loading();

    // AsyncValue.guard automatically catches errors
    state = await AsyncValue.guard(() async {
      await Future.delayed(const Duration(milliseconds: 500));
      return newData;
    });

    // Handle the result
    state.when(
      data: (data) {
        // Success
        print('Updated successfully: $data');
      },
      loading: () {
        // Still loading
      },
      error: (error, stack) {
        // Handle error
        print('Update failed: $error');
      },
    );
  }
}

// ============================================================================
// USAGE IN WIDGETS
// ============================================================================

/*

// 1. Watching a simple provider
final version = ref.watch(appVersionProvider);

// 2. Watching async provider
final userData = ref.watch(userDataProvider);
userData.when(
  data: (data) => Text(data),
  loading: () => const CustomLoadingIndicator(),
  error: (error, stack) => Text('Error: $error'),
);

// 3. Watching with family (parameters)
final profile = ref.watch(userProfileProvider('user123'));

// 4. Watching state from Notifier
final count = ref.watch(counterProvider);

// 5. Calling Notifier methods
ref.read(counterProvider.notifier).increment();

// 6. Watching AsyncNotifier state
final profileState = ref.watch(userProfileNotifierProvider);

// 7. Calling AsyncNotifier methods
await ref.read(userProfileNotifierProvider.notifier).updateName('New Name');

// 8. Selective watching for optimization
final userName = ref.watch(
  userProfileNotifierProvider.select((async) => async.value?.name),
);

// 9. Invalidating a provider
ref.invalidate(userDataProvider);

// 10. Refreshing a provider
ref.refresh(userDataProvider);

// 11. Pattern matching (Dart 3.0+)
final profileState = ref.watch(userProfileNotifierProvider);
switch (profileState) {
  case AsyncData(:final value):
    return Text(value.name);
  case AsyncError(:final error):
    return Text('Error: $error');
  case AsyncLoading():
    return const CustomLoadingIndicator();
}

*/
