extension IterableExt<T> on Iterable<T> {
  Iterable<T> separated(T separator) sync* {
    final iterator = this.iterator;
    if (!iterator.moveNext()) return;

    yield iterator.current;

    while (iterator.moveNext()) {
      yield separator;
      yield iterator.current;
    }
  }

  Iterable<List<T>> chunks(int size) sync* {
    if (length == 0) return;
    var iterator = this.iterator;
    while (iterator.moveNext()) {
      var chunk = [iterator.current];
      for (var i = 1; i < size && iterator.moveNext(); i++) {
        chunk.add(iterator.current);
      }
      yield chunk;
    }
  }

  Iterable<T> fill(int length, {required T Function(int count) filler}) sync* {
    int count = 0;
    for (var item in this) {
      yield item;
      count++;
      if (count >= length) return;
    }
    while (count < length) {
      yield filler(count);
      count++;
    }
  }

  Iterable<T> takeLast({int count = 50}) {
    if (count <= 0) return Iterable.empty();
    return count >= length ? this : toList().skip(length - count);
  }
}

extension ListExt<T> on List<T> {
  void truncate(int maxLength) {
    if (maxLength == 0) {
      return;
    }
    if (length > maxLength) {
      removeRange(0, length - maxLength);
    }
  }

  List<T> intersection(List<T> list) {
    return where((item) => list.contains(item)).toList();
  }

  List<List<T>> batch(int maxConcurrent) {
    final batches = (length / maxConcurrent).ceil();
    final List<List<T>> res = [];
    for (int i = 0; i < batches; i++) {
      if (i != batches - 1) {
        res.add(sublist(i * maxConcurrent, maxConcurrent * (i + 1)));
      } else {
        res.add(sublist(i * maxConcurrent, length));
      }
    }
    return res;
  }

  List<T> safeSublist(int start, [int? end]) {
    if (start <= 0) return this;
    if (start > length) return [];
    if (end != null) {
      return sublist(start, end.clamp(start, length));
    }
    return sublist(start);
  }

  T safeGet(int index) {
    if (length > index) return this[index];
    return last;
  }

  T safeLast(T value) {
    if (isNotEmpty) {
      return last;
    }
    return value;
  }

  void addOrRemove(T value) {
    if (contains(value)) {
      remove(value);
    } else {
      add(value);
    }
  }
}

extension SetExt<T> on Set<T> {
  void addOrRemove(T value) {
    if (contains(value)) {
      remove(value);
    } else {
      add(value);
    }
  }
}

extension DoubleListExt on List<double> {
  int findInterval(num target) {
    if (isEmpty) return -1;
    if (target < first) return -1;
    if (target >= last) return length - 1;

    int left = 0;
    int right = length - 1;

    while (left <= right) {
      int mid = left + (right - left) ~/ 2;

      if (mid == length - 1 ||
          (this[mid] <= target && target < this[mid + 1])) {
        return mid;
      } else if (target < this[mid]) {
        right = mid - 1;
      } else {
        left = mid + 1;
      }
    }

    return -1;
  }
}

extension MapExt<K, V> on Map<K, V> {
  V updateCacheValue(K key, V Function() callback) {
    if (this[key] == null) {
      this[key] = callback();
    }
    return this[key]!;
  }

  Map<K, V> copyWitUpdate(K key, V? value) {
    final newMap = Map<K, V>.from(this);
    if (value == null) {
      newMap.remove(key);
    } else {
      newMap[key] = value;
    }
    return newMap;
  }
}
