content: import 'dart:async';
  import 'dart:math';
  
  import 'package:hooks_riverpod/hooks_riverpod.dart';
  import 'package:spotube/models/metadata/metadata.dart';
  import 'package:spotube/provider/metadata_plugin/utils/common.dart';
  import 'package:spotube/services/logger/logger.dart';
  
  abstract class FamilyPaginatedAsyncNotifier<K, A>
      extends FamilyAsyncNotifier<SpotubePaginationResponseObject<K>, A>
      with MetadataPluginMixin<K> {
    Future<SpotubePaginationResponseObject<K>> fetch(int offset, int limit);
  
    Future<void> fetchMore() async {
      if (state.value == null || !state.value!.hasMore) return;
  
      final oldState = state.value;
  
      try {
        state = AsyncLoadingNext(state.asData!.value);
  
        final newState = await fetch(
          state.value!.nextOffset!,
          state.value!.limit,
        );
  
        final oldItems =
            state.value!.items.isEmpty ? <K>[] : state.value!.items.cast<K>();
        final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>();
  
        state = AsyncData(newState.copyWith(items: <K>[...oldItems, ...items]));
      } catch (e, stack) {
        AppLogger.reportError(e, stack);
        state = AsyncData(oldState!);
      }
    }
  
    Future<List<K>> fetchAll() async {
      if (state.value == null) return [];
      if (!state.value!.hasMore) return state.value!.items.cast<K>();
  
      bool hasMore = true;
      while (hasMore) {
        final newState = await fetch(
          state.value!.nextOffset!,
          max(state.value!.limit, 100),
        )
            .catchError(
              (e) => fetch(state.value!.nextOffset!, max(state.value!.limit, 50)),
            )
            .catchError(
              (e) => fetch(state.value!.nextOffset!, state.value!.limit),
            )
            .catchError(
          (e) async {
            await Future.delayed(const Duration(milliseconds: 500));
            return fetch(state.value!.nextOffset!, state.value!.limit);
          },
        );
  
        hasMore = newState.hasMore;
  
        final oldItems =
            state.value!.items.isEmpty ? <K>[] : state.value!.items.cast<K>();
        final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>();
  
        state = AsyncData(
          newState.copyWith(items: [...oldItems, ...items]),
        );
      }
  
      return state.value!.items.cast<K>();
    }
  }
  
  abstract class AutoDisposeFamilyPaginatedAsyncNotifier<K, A>
      extends AutoDisposeFamilyAsyncNotifier<SpotubePaginationResponseObject<K>,
          A> with MetadataPluginMixin<K> {
    Future<SpotubePaginationResponseObject<K>> fetch(int offset, int limit);
  
    Future<void> fetchMore() async {
      if (state.value == null || !state.value!.hasMore) return;
      final oldState = state.value;
  
      try {
        state = AsyncLoadingNext(state.value!);
  
        final newState = await fetch(
          state.value!.nextOffset!,
          state.value!.limit,
        );
  
        state = AsyncData(
          newState.copyWith(items: [
            ...state.value!.items.cast<K>(),
            ...newState.items.cast<K>(),
          ]),
        );
      } catch (e, stack) {
        AppLogger.reportError(e, stack);
        state = AsyncData(oldState!);
      }
    }
  
    Future<List<K>> fetchAll() async {
      if (state.value == null) return [];
      if (!state.value!.hasMore) return state.value!.items.cast<K>();
  
      bool hasMore = true;
      while (hasMore) {
        final newState = await fetch(
          state.value!.nextOffset!,
          max(state.value!.limit, 100),
        )
            .catchError(
              (e) => fetch(state.value!.nextOffset!, max(state.value!.limit, 50)),
            )
            .catchError(
              (e) => fetch(state.value!.nextOffset!, state.value!.limit),
            )
            .catchError(
          (e) async {
            await Future.delayed(const Duration(milliseconds: 500));
            return fetch(state.value!.nextOffset!, state.value!.limit);
          },
        );
  
        hasMore = newState.hasMore;
  
        final oldItems =
            state.value!.items.isEmpty ? <K>[] : state.value!.items.cast<K>();
        final items = newState.items.isEmpty ? <K>[] : newState.items.cast<K>();
  
        state = AsyncData(
          newState.copyWith(items: [...oldItems, ...items]),
        );
      }
  
      return state.value!.items.cast<K>();
    }
  }
  