import 'package:flutter/foundation.dart';
import 'package:nc_photos/override_comparator.dart';
import 'package:tuple/tuple.dart';

extension IterableExtension<T> on Iterable<T> {
  /// Return a new sorted list
  List<T> sorted([int Function(T a, T b)? compare]) => toList()..sort(compare);

  /// Return a new stable sorted list
  List<T> stableSorted([int Function(T a, T b)? compare]) {
    final tmp = toList();
    mergeSort(tmp, compare: compare);
    return tmp;
  }

  /// Return a string representation of this iterable by joining the result of
  /// toString for each items
  String toReadableString() => "[${join(', ')}]";

  Iterable<U> mapWithIndex<U>(U Function(int index, T element) fn) sync* {
    int i = 0;
    for (final e in this) {
      yield fn(i++, e);
    }
  }

  Iterable<Tuple2<int, T>> withIndex() => mapWithIndex((i, e) => Tuple2(i, e));

  /// Whether the collection contains an element equal to [element] using the
  /// equality function [equalFn]
  bool containsIf(T element, bool Function(T a, T b) equalFn) =>
      any((e) => equalFn(e, element));

  /// Same as [contains] but uses [identical] to compare the objects
  bool containsIdentical(T element) =>
      containsIf(element, (a, b) => identical(a, b));

  Iterable<Tuple2<U, List<T>>> groupBy<U>({required U Function(T e) key}) {
    final map = fold<Map<U, List<T>>>(
        {},
        (previousValue, element) =>
            previousValue..putIfAbsent(key(element), () => []).add(element));
    return map.entries.map((e) => Tuple2(e.key, e.value));
  }

  /// Return a new list with only distinct elements
  List<T> distinct() {
    final s = <T>{};
    return where((element) => s.add(element)).toList();
  }

  /// Return a new list with only distinct elements determined by [equalFn]
  List<T> distinctIf(
      bool Function(T a, T b) equalFn, int Function(T a) hashCodeFn) {
    final s = <OverrideComparator<T>>{};
    return where((element) =>
        s.add(OverrideComparator<T>(element, equalFn, hashCodeFn))).toList();
  }

  T? get firstOrNull {
    try {
      return first;
    } on StateError catch (_) {
      return null;
    }
  }

  T? get lastOrNull {
    try {
      return last;
    } on StateError catch (_) {
      return null;
    }
  }
}