/**
 * This file provides type definitions for use with the Flow type checker.
 *
 * An important caveat when using these definitions is that the types for
 * `Collection.Keyed`, `Collection.Indexed`, `Seq.Keyed`, and so on are stubs.
 * When referring to those types, you can get the proper definitions by
 * importing the types `KeyedCollection`, `IndexedCollection`, `KeyedSeq`, etc.
 * For example,
 *
 *     import { Seq } from 'immutable'
 *     import type { IndexedCollection, IndexedSeq } from 'immutable'
 *
 *     const someSeq: IndexedSeq<number> = Seq.Indexed.of(1, 2, 3)
 *
 *     function takesASeq<T, TS: IndexedCollection<T>>(iter: TS): TS {
 *       return iter.butLast()
 *     }
 *
 *     takesASeq(someSeq)
 *
 * @flow
 */

declare class _Collection<K, +V> /*implements ValueObject*/ {
  equals(other: mixed): boolean;
  hashCode(): number;
  get(key: K, ..._: []): V | void;
  get<NSV>(key: K, notSetValue: NSV): V | NSV;
  has(key: K): boolean;
  includes(value: V): boolean;
  contains(value: V): boolean;
  first(): V | void;
  last(): V | void;

  getIn(searchKeyPath: Iterable<mixed>, notSetValue?: mixed): any;
  hasIn(searchKeyPath: Iterable<mixed>): boolean;

  update<U>(updater: (value: this) => U): U;

  toJS(): Array<any> | { [key: string]: mixed };
  toJSON(): Array<V> | { [key: string]: V };
  toArray(): Array<V>;
  toObject(): { [key: string]: V };
  toMap(): Map<K, V>;
  toOrderedMap(): OrderedMap<K, V>;
  toSet(): Set<V>;
  toOrderedSet(): OrderedSet<V>;
  toList(): List<V>;
  toStack(): Stack<V>;
  toSeq(): Seq<K, V>;
  toKeyedSeq(): KeyedSeq<K, V>;
  toIndexedSeq(): IndexedSeq<V>;
  toSetSeq(): SetSeq<V>;

  keys(): Iterator<K>;
  values(): Iterator<V>;
  entries(): Iterator<[K, V]>;

  keySeq(): IndexedSeq<K>;
  valueSeq(): IndexedSeq<V>;
  entrySeq(): IndexedSeq<[K, V]>;

  reverse(): this;
  sort(comparator?: (valueA: V, valueB: V) => number): this;

  sortBy<C>(
    comparatorValueMapper: (value: V, key: K, iter: this) => C,
    comparator?: (valueA: C, valueB: C) => number
  ): this;

  groupBy<G>(
    grouper: (value: V, key: K, iter: this) => G,
    context?: mixed
  ): KeyedSeq<G, this>;

  forEach(
    sideEffect: (value: V, key: K, iter: this) => any,
    context?: mixed
  ): number;

  slice(begin?: number, end?: number): this;
  rest(): this;
  butLast(): this;
  skip(amount: number): this;
  skipLast(amount: number): this;
  skipWhile(predicate: (value: V, key: K, iter: this) => mixed, context?: mixed): this;
  skipUntil(predicate: (value: V, key: K, iter: this) => mixed, context?: mixed): this;
  take(amount: number): this;
  takeLast(amount: number): this;
  takeWhile(predicate: (value: V, key: K, iter: this) => mixed, context?: mixed): this;
  takeUntil(predicate: (value: V, key: K, iter: this) => mixed, context?: mixed): this;

  filter(
    predicate: (value: V, key: K, iter: this) => mixed,
    context?: mixed
  ): this;

  filterNot(
    predicate: (value: V, key: K, iter: this) => mixed,
    context?: mixed
  ): this;

  reduce<R>(
    reducer: (reduction: R, value: V, key: K, iter: this) => R,
    initialReduction: R,
    context?: mixed,
  ): R;
  reduce<R>(
    reducer: (reduction: V | R, value: V, key: K, iter: this) => R
  ): R;

  reduceRight<R>(
    reducer: (reduction: R, value: V, key: K, iter: this) => R,
    initialReduction: R,
    context?: mixed,
  ): R;
  reduceRight<R>(
    reducer: (reduction: V | R, value: V, key: K, iter: this) => R
  ): R;

  every(predicate: (value: V, key: K, iter: this) => mixed, context?: mixed): boolean;
  some(predicate: (value: V, key: K, iter: this) => mixed, context?: mixed): boolean;
  join(separator?: string): string;
  isEmpty(): boolean;
  count(predicate?: (value: V, key: K, iter: this) => mixed, context?: mixed): number;
  countBy<G>(grouper: (value: V, key: K, iter: this) => G, context?: mixed): Map<G, number>;

  find<NSV>(
    predicate: (value: V, key: K, iter: this) => mixed,
    context?: mixed,
    notSetValue?: NSV
  ): V | NSV;
  findLast<NSV>(
    predicate: (value: V, key: K, iter: this) => mixed,
    context?: mixed,
    notSetValue?: NSV
  ): V | NSV;

  findEntry(predicate: (value: V, key: K, iter: this) => mixed): [K, V] | void;
  findLastEntry(predicate: (value: V, key: K, iter: this) => mixed): [K, V] | void;

  findKey(predicate: (value: V, key: K, iter: this) => mixed, context?: mixed): K | void;
  findLastKey(predicate: (value: V, key: K, iter: this) => mixed, context?: mixed): K | void;

  keyOf(searchValue: V): K | void;
  lastKeyOf(searchValue: V): K | void;

  max(comparator?: (valueA: V, valueB: V) => number): V;
  maxBy<C>(
    comparatorValueMapper: (value: V, key: K, iter: this) => C,
    comparator?: (valueA: C, valueB: C) => number
  ): V;
  min(comparator?: (valueA: V, valueB: V) => number): V;
  minBy<C>(
    comparatorValueMapper: (value: V, key: K, iter: this) => C,
    comparator?: (valueA: C, valueB: C) => number
  ): V;

  isSubset(iter: Iterable<V>): boolean;
  isSuperset(iter: Iterable<V>): boolean;
}

declare function isImmutable(maybeImmutable: mixed): boolean %checks(maybeImmutable instanceof Collection);
declare function isCollection(maybeCollection: mixed): boolean %checks(maybeCollection instanceof Collection);
declare function isKeyed(maybeKeyed: mixed): boolean %checks(maybeKeyed instanceof KeyedCollection);
declare function isIndexed(maybeIndexed: mixed): boolean %checks(maybeIndexed instanceof IndexedCollection);
declare function isAssociative(maybeAssociative: mixed): boolean %checks(
  maybeAssociative instanceof KeyedCollection ||
  maybeAssociative instanceof IndexedCollection
);
declare function isOrdered(maybeOrdered: mixed): boolean %checks(
  maybeOrdered instanceof IndexedCollection ||
  maybeOrdered instanceof OrderedMap ||
  maybeOrdered instanceof OrderedSet
);
declare function isValueObject(maybeValue: mixed): boolean;

declare interface ValueObject {
  equals(other: mixed): boolean;
  hashCode(): number;
}

declare class Collection<K, +V> extends _Collection<K, V> {
  static Keyed: typeof KeyedCollection;
  static Indexed: typeof IndexedCollection;
  static Set: typeof SetCollection;

  static isCollection: typeof isCollection;
  static isKeyed: typeof isKeyed;
  static isIndexed: typeof isIndexed;
  static isAssociative: typeof isAssociative;
  static isOrdered: typeof isOrdered;
}

declare class KeyedCollection<K, +V> extends Collection<K, V> {
  static <K, V>(iter?: Iterable<[K, V]>): KeyedCollection<K, V>;
  static <K, V>(obj?: { [key: K]: V }): KeyedCollection<K, V>;

  toJS(): { [key: string]: mixed };
  toJSON(): { [key: string]: V };
  @@iterator(): Iterator<[K, V]>;
  toSeq(): KeyedSeq<K, V>;
  flip(): KeyedCollection<V, K>;

  concat<KC, VC>(...iters: Array<Iterable<[KC, VC]>>): KeyedCollection<K | KC, V | VC>;
  concat<C>(...iters: Array<{[key: string]: C}>): KeyedCollection<K | string, V | C>;

  map<M>(
    mapper: (value: V, key: K, iter: this) => M,
    context?: mixed
  ): KeyedCollection<K, M>;

  mapKeys<M>(
    mapper: (key: K, value: V, iter: this) => M,
    context?: mixed
  ): KeyedCollection<M, V>;

  mapEntries<KM, VM>(
    mapper: (entry: [K, V], index: number, iter: this) => [KM, VM],
    context?: mixed
  ): KeyedCollection<KM, VM>;

  flatMap<KM, VM>(
    mapper: (value: V, key: K, iter: this) => Iterable<[KM, VM]>,
    context?: mixed
  ): KeyedCollection<KM, VM>;

  flatten(depth?: number): KeyedCollection<any, any>;
  flatten(shallow?: boolean): KeyedCollection<any, any>;
}

Collection.Keyed = KeyedCollection

declare class IndexedCollection<+T> extends Collection<number, T> {
  static <T>(iter?: Iterable<T>): IndexedCollection<T>;

  toJS(): Array<mixed>;
  toJSON(): Array<T>;
  @@iterator(): Iterator<T>;
  toSeq(): IndexedSeq<T>;
  fromEntrySeq<K, V>(): KeyedSeq<K, V>;
  interpose(separator: T): this;
  interleave(...collections: Iterable<T>[]): this;
  splice(
    index: number,
    removeNum: number,
    ...values: T[]
  ): this;

  zip<A>(
    a: Iterable<A>,
    ..._: []
  ): IndexedCollection<[T, A]>;
  zip<A, B>(
    a: Iterable<A>,
    b: Iterable<B>,
    ..._: []
  ): IndexedCollection<[T, A, B]>;
  zip<A, B, C>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    ..._: []
  ): IndexedCollection<[T, A, B, C]>;
  zip<A, B, C, D>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    ..._: []
  ): IndexedCollection<[T, A, B, C, D]>;
  zip<A, B, C, D, E>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    e: Iterable<E>,
    ..._: []
  ): IndexedCollection<[T, A, B, C, D, E]>;

  zipWith<A, R>(
    zipper: (value: T, a: A) => R,
    a: Iterable<A>,
    ..._: []
  ): IndexedCollection<R>;
  zipWith<A, B, R>(
    zipper: (value: T, a: A, b: B) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    ..._: []
  ): IndexedCollection<R>;
  zipWith<A, B, C, R>(
    zipper: (value: T, a: A, b: B, c: C) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    ..._: []
  ): IndexedCollection<R>;
  zipWith<A, B, C, D, R>(
    zipper: (value: T, a: A, b: B, c: C, d: D) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    ..._: []
  ): IndexedCollection<R>;
  zipWith<A, B, C, D, E, R>(
    zipper: (value: T, a: A, b: B, c: C, d: D, e: E) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    e: Iterable<E>,
    ..._: []
  ): IndexedCollection<R>;

  indexOf(searchValue: T): number;
  lastIndexOf(searchValue: T): number;
  findIndex(
    predicate: (value: T, index: number, iter: this) => mixed,
    context?: mixed
  ): number;
  findLastIndex(
    predicate: (value: T, index: number, iter: this) => mixed,
    context?: mixed
  ): number;

  concat<C>(...iters: Array<Iterable<C> | C>): IndexedCollection<T | C>;

  map<M>(
    mapper: (value: T, index: number, iter: this) => M,
    context?: mixed
  ): IndexedCollection<M>;

  flatMap<M>(
    mapper: (value: T, index: number, iter: this) => Iterable<M>,
    context?: mixed
  ): IndexedCollection<M>;

  flatten(depth?: number): IndexedCollection<any>;
  flatten(shallow?: boolean): IndexedCollection<any>;
}

declare class SetCollection<+T> extends Collection<T, T> {
  static <T>(iter?: Iterable<T>): SetCollection<T>;

  toJS(): Array<mixed>;
  toJSON(): Array<T>;
  @@iterator(): Iterator<T>;
  toSeq(): SetSeq<T>;

  concat<C>(...iters: Array<Iterable<C> | C>): SetCollection<T | C>;

  // `map` and `flatMap` cannot be defined further up the hiearchy, because the
  // implementation for `KeyedCollection` allows the value type to change without
  // constraining the key type. That does not work for `SetCollection` - the value
  // and key types *must* match.
  map<M>(
    mapper: (value: T, value: T, iter: this) => M,
    context?: mixed
  ): SetCollection<M>;

  flatMap<M>(
    mapper: (value: T, value: T, iter: this) => Iterable<M>,
    context?: mixed
  ): SetCollection<M>;

  flatten(depth?: number): SetCollection<any>;
  flatten(shallow?: boolean): SetCollection<any>;
}

declare function isSeq(maybeSeq: mixed): boolean %checks(maybeSeq instanceof Seq);
declare class Seq<K, +V> extends _Collection<K, V> {
  static Keyed: typeof KeyedSeq;
  static Indexed: typeof IndexedSeq;
  static Set: typeof SetSeq;

  static <K, V>(iter: KeyedSeq<K, V>): KeyedSeq<K, V>;
  static <T>(iter: SetSeq<T>): SetSeq<K, V>;
  static <T>(iter?: Iterable<T>): IndexedSeq<T>;
  static <K, V>(iter: { [key: K]: V }): KeyedSeq<K, V>;

  static of<T>(...values: T[]): IndexedSeq<T>;

  static isSeq: typeof isSeq;

  size: number | void;
  cacheResult(): this;
  toSeq(): this;
}

declare class KeyedSeq<K, +V> extends Seq<K, V> mixins KeyedCollection<K, V> {
  static <K, V>(iter?: Iterable<[K, V]>): KeyedSeq<K, V>;
  static <K, V>(iter?: { [key: K]: V }): KeyedSeq<K, V>;

  // Override specialized return types
  flip(): KeyedSeq<V, K>;

  concat<KC, VC>(...iters: Array<Iterable<[KC, VC]>>): KeyedSeq<K | KC, V | VC>;
  concat<C>(...iters: Array<{[key: string]: C}>): KeyedSeq<K | string, V | C>;

  map<M>(
    mapper: (value: V, key: K, iter: this) => M,
    context?: mixed
  ): KeyedSeq<K, M>;

  mapKeys<M>(
    mapper: (key: K, value: V, iter: this) => M,
    context?: mixed
  ): KeyedSeq<M, V>;

  mapEntries<KM, VM>(
    mapper: (entry: [K, V], index: number, iter: this) => [KM, VM],
    context?: mixed
  ): KeyedSeq<KM, VM>;

  flatMap<KM, VM>(
    mapper: (value: V, key: K, iter: this) => Iterable<[KM, VM]>,
    context?: mixed
  ): KeyedSeq<KM, VM>;

  flatten(depth?: number): KeyedSeq<any, any>;
  flatten(shallow?: boolean): KeyedSeq<any, any>;
}

declare class IndexedSeq<+T> extends Seq<number, T> mixins IndexedCollection<T> {
  static <T>(iter?: Iterable<T>): IndexedSeq<T>;

  static of<T>(...values: T[]): IndexedSeq<T>;

  // Override specialized return types

  concat<C>(...iters: Array<Iterable<C> | C>): IndexedSeq<T | C>;

  map<M>(
    mapper: (value: T, index: number, iter: this) => M,
    context?: mixed
  ): IndexedSeq<M>;

  flatMap<M>(
    mapper: (value: T, index: number, iter: this) => Iterable<M>,
    context?: mixed
  ): IndexedSeq<M>;

  flatten(depth?: number): IndexedSeq<any>;
  flatten(shallow?: boolean): IndexedSeq<any>;

  zip<A>(
    a: Iterable<A>,
    ..._: []
  ): IndexedSeq<[T, A]>;
  zip<A, B>(
    a: Iterable<A>,
    b: Iterable<B>,
    ..._: []
  ): IndexedSeq<[T, A, B]>;
  zip<A, B, C>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    ..._: []
  ): IndexedSeq<[T, A, B, C]>;
  zip<A, B, C, D>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    ..._: []
  ): IndexedSeq<[T, A, B, C, D]>;
  zip<A, B, C, D, E>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    e: Iterable<E>,
    ..._: []
  ): IndexedSeq<[T, A, B, C, D, E]>;

  zipWith<A, R>(
    zipper: (value: T, a: A) => R,
    a: Iterable<A>,
    ..._: []
  ): IndexedSeq<R>;
  zipWith<A, B, R>(
    zipper: (value: T, a: A, b: B) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    ..._: []
  ): IndexedSeq<R>;
  zipWith<A, B, C, R>(
    zipper: (value: T, a: A, b: B, c: C) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    ..._: []
  ): IndexedSeq<R>;
  zipWith<A, B, C, D, R>(
    zipper: (value: T, a: A, b: B, c: C, d: D) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    ..._: []
  ): IndexedSeq<R>;
  zipWith<A, B, C, D, E, R>(
    zipper: (value: T, a: A, b: B, c: C, d: D, e: E) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    e: Iterable<E>,
    ..._: []
  ): IndexedSeq<R>;
}

declare class SetSeq<+T> extends Seq<T, T> mixins SetCollection<T> {
  static <T>(iter?: Iterable<T>): IndexedSeq<T>;

  static of<T>(...values: T[]): SetSeq<T>;

  // Override specialized return types

  concat<C>(...iters: Array<Iterable<C> | C>): SetSeq<T | C>;

  map<M>(
    mapper: (value: T, value: T, iter: this) => M,
    context?: mixed
  ): SetSeq<M>;

  flatMap<M>(
    mapper: (value: T, value: T, iter: this) => Iterable<M>,
    context?: mixed
  ): SetSeq<M>;

  flatten(depth?: number): SetSeq<any>;
  flatten(shallow?: boolean): SetSeq<any>;
}

declare function isList(maybeList: mixed): boolean %checks(maybeList instanceof List);
declare class List<+T> extends IndexedCollection<T> {
  static (collection?: Iterable<T>): List<T>;

  static of<T>(...values: T[]): List<T>;

  static isList: typeof isList;

  size: number;

  set<U>(index: number, value: U): List<T | U>;
  delete(index: number): this;
  remove(index: number): this;
  insert<U>(index: number, value: U): List<T | U>;
  clear(): this;
  push<U>(...values: U[]): List<T | U>;
  pop(): this;
  unshift<U>(...values: U[]): List<T | U>;
  shift(): this;

  update<U>(updater: (value: this) => U): U;
  update<U>(index: number, updater: (value: T) => U): List<T | U>;
  update<U>(index: number, notSetValue: U, updater: (value: T) => U): List<T | U>;

  merge<U>(...collections: Iterable<U>[]): List<T | U>;

  mergeWith<U, V>(
    merger: (oldVal: T, newVal: U, key: number) => V,
    ...collections: Iterable<U>[]
  ): List<T | U | V>;

  mergeDeep<U>(...collections: Iterable<U>[]): List<T | U>;

  mergeDeepWith<U, V>(
    merger: (oldVal: T, newVal: U, key: number) => V,
    ...collections: Iterable<U>[]
  ): List<T | U | V>;

  setSize(size: number): this;
  setIn(keyPath: Iterable<mixed>, value: mixed): this;
  deleteIn(keyPath: Iterable<mixed>, value: mixed): this;
  removeIn(keyPath: Iterable<mixed>, value: mixed): this;

  updateIn(
    keyPath: Iterable<mixed>,
    notSetValue: mixed,
    updater: (value: any) => mixed
  ): this;
  updateIn(
    keyPath: Iterable<mixed>,
    updater: (value: any) => mixed
  ): this;

  mergeIn(keyPath: Iterable<mixed>, ...collections: Iterable<mixed>[]): this;
  mergeDeepIn(keyPath: Iterable<mixed>, ...collections: Iterable<mixed>[]): this;

  withMutations(mutator: (mutable: this) => mixed): this;
  asMutable(): this;
  asImmutable(): this;

  // Override specialized return types

  concat<C>(...iters: Array<Iterable<C> | C>): List<T | C>;

  map<M>(
    mapper: (value: T, index: number, iter: this) => M,
    context?: mixed
  ): List<M>;

  flatMap<M>(
    mapper: (value: T, index: number, iter: this) => Iterable<M>,
    context?: mixed
  ): List<M>;

  flatten(depth?: number): List<any>;
  flatten(shallow?: boolean): List<any>;

  zip<A>(
    a: Iterable<A>,
    ..._: []
  ): List<[T, A]>;
  zip<A, B>(
    a: Iterable<A>,
    b: Iterable<B>,
    ..._: []
  ): List<[T, A, B]>;
  zip<A, B, C>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    ..._: []
  ): List<[T, A, B, C]>;
  zip<A, B, C, D>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    ..._: []
  ): List<[T, A, B, C, D]>;
  zip<A, B, C, D, E>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    e: Iterable<E>,
    ..._: []
  ): List<[T, A, B, C, D, E]>;

  zipWith<A, R>(
    zipper: (value: T, a: A) => R,
    a: Iterable<A>,
    ..._: []
  ): List<R>;
  zipWith<A, B, R>(
    zipper: (value: T, a: A, b: B) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    ..._: []
  ): List<R>;
  zipWith<A, B, C, R>(
    zipper: (value: T, a: A, b: B, c: C) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    ..._: []
  ): List<R>;
  zipWith<A, B, C, D, R>(
    zipper: (value: T, a: A, b: B, c: C, d: D) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    ..._: []
  ): List<R>;
  zipWith<A, B, C, D, E, R>(
    zipper: (value: T, a: A, b: B, c: C, d: D, e: E) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    e: Iterable<E>,
    ..._: []
  ): List<R>;
}

declare function isMap(maybeMap: mixed): boolean %checks(maybeMap instanceof Map);
declare class Map<K, +V> extends KeyedCollection<K, V> {
  static <K, V>(obj?: {[key: K]: V}): Map<K, V>;
  static <K, V>(collection: Iterable<[K, V]>): Map<K, V>;

  static isMap: typeof isMap;

  size: number;

  set<K_, V_>(key: K_, value: V_): Map<K | K_, V | V_>;
  delete(key: K): this;
  remove(key: K): this;
  clear(): this;

  deleteAll(keys: Iterable<K>): Map<K, V>;
  removeAll(keys: Iterable<K>): Map<K, V>;

  update<U>(updater: (value: this) => U): U;
  update<V_>(key: K, updater: (value: V) => V_): Map<K, V | V_>;
  update<V_>(key: K, notSetValue: V_, updater: (value: V) => V_): Map<K, V | V_>;

  merge<K_, V_>(
    ...collections: (Iterable<[K_, V_]> | { [key: K_]: V_ })[]
  ): Map<K | K_, V | V_>;

  mergeWith<K_, W, X>(
    merger: (oldVal: V, newVal: W, key: K) => X,
    ...collections: (Iterable<[K_, W]> | { [key: K_]: W })[]
  ): Map<K | K_, V | W | X>;

  mergeDeep<K_, V_>(
    ...collections: (Iterable<[K_, V_]> | { [key: K_]: V_ })[]
  ): Map<K | K_, V | V_>;

  mergeDeepWith<K_, W, X>(
    merger: (oldVal: V, newVal: W, key: K) => X,
    ...collections: (Iterable<[K_, W]> | { [key: K_]: W })[]
  ): Map<K | K_, V | W | X>;

  setIn(keyPath: Iterable<mixed>, value: mixed): this;
  deleteIn(keyPath: Iterable<mixed>, value: mixed): this;
  removeIn(keyPath: Iterable<mixed>, value: mixed): this;

  updateIn(
    keyPath: Iterable<mixed>,
    notSetValue: mixed,
    updater: (value: any) => mixed
  ): this;
  updateIn(
    keyPath: Iterable<mixed>,
    updater: (value: any) => mixed
  ): this;

  mergeIn(
    keyPath: Iterable<K>,
    ...collections: (Iterable<K> | { [key: string]: mixed })[]
  ): this;
  mergeDeepIn(
    keyPath: Iterable<K>,
    ...collections: (Iterable<K> | { [key: string]: mixed })[]
  ): this;

  withMutations(mutator: (mutable: this) => mixed): this;
  asMutable(): this;
  asImmutable(): this;

  // Override specialized return types

  flip(): Map<V, K>;

  concat<KC, VC>(...iters: Array<Iterable<[KC, VC]>>): Map<K | KC, V | VC>;
  concat<C>(...iters: Array<{[key: string]: C}>): Map<K | string, V | C>;

  map<M>(
    mapper: (value: V, key: K, iter: this) => M,
    context?: mixed
  ): Map<K, M>;

  mapKeys<M>(
    mapper: (key: K, value: V, iter: this) => M,
    context?: mixed
  ): Map<M, V>;

  mapEntries<KM, VM>(
    mapper: (entry: [K, V], index: number, iter: this) => [KM, VM],
    context?: mixed
  ): Map<KM, VM>;

  flatMap<KM, VM>(
    mapper: (value: V, key: K, iter: this) => Iterable<[KM, VM]>,
    context?: mixed
  ): Map<KM, VM>;

  flatten(depth?: number): Map<any, any>;
  flatten(shallow?: boolean): Map<any, any>;
}

declare function isOrderedMap(maybeOrderedMap: mixed): boolean %checks(maybeOrderedMap instanceof OrderedMap);
declare class OrderedMap<K, +V> extends Map<K, V> {
  static <K, V>(obj?: {[key: K]: V}): OrderedMap<K, V>;
  static <K, V>(collection: Iterable<[K, V]>): OrderedMap<K, V>;

  static isOrderedMap: typeof isOrderedMap;

  size: number;

  set<K_, V_>(key: K_, value: V_): OrderedMap<K | K_, V | V_>;
  delete(key: K): this;
  remove(key: K): this;
  clear(): this;

  update<U>(updater: (value: this) => U): U;
  update<V_>(key: K, updater: (value: V) => V_): OrderedMap<K, V | V_>;
  update<V_>(key: K, notSetValue: V_, updater: (value: V) => V_): OrderedMap<K, V | V_>;

  merge<K_, V_>(
    ...collections: (Iterable<[K_, V_]> | { [key: K_]: V_ })[]
  ): OrderedMap<K | K_, V | V_>;

  mergeWith<K_, W, X>(
    merger: (oldVal: V, newVal: W, key: K) => X,
    ...collections: (Iterable<[K_, W]> | { [key: K_]: W })[]
  ): OrderedMap<K | K_, V | W | X>;

  mergeDeep<K_, V_>(
    ...collections: (Iterable<[K_, V_]> | { [key: K_]: V_ })[]
  ): OrderedMap<K | K_, V | V_>;

  mergeDeepWith<K_, W, X>(
    merger: (oldVal: V, newVal: W, key: K) => X,
    ...collections: (Iterable<[K_, W]> | { [key: K_]: W })[]
  ): OrderedMap<K | K_, V | W | X>;

  setIn(keyPath: Iterable<mixed>, value: mixed): this;
  deleteIn(keyPath: Iterable<mixed>, value: mixed): this;
  removeIn(keyPath: Iterable<mixed>, value: mixed): this;

  updateIn(
    keyPath: Iterable<mixed>,
    notSetValue: mixed,
    updater: (value: any) => mixed
  ): this;
  updateIn(
    keyPath: Iterable<mixed>,
    updater: (value: any) => mixed
  ): this;

  mergeIn(
    keyPath: Iterable<K>,
    ...collections: (Iterable<K> | { [key: string]: mixed })[]
  ): this;
  mergeDeepIn(
    keyPath: Iterable<K>,
    ...collections: (Iterable<K> | { [key: string]: mixed })[]
  ): this;

  withMutations(mutator: (mutable: this) => mixed): this;
  asMutable(): this;
  asImmutable(): this;

  // Override specialized return types

  flip(): OrderedMap<V, K>;

  concat<KC, VC>(...iters: Array<Iterable<[KC, VC]>>): OrderedMap<K | KC, V | VC>;
  concat<C>(...iters: Array<{[key: string]: C}>): OrderedMap<K | string, V | C>;

  map<M>(
    mapper: (value: V, key: K, iter: this) => M,
    context?: mixed
  ): OrderedMap<K, M>;

  mapKeys<M>(
    mapper: (key: K, value: V, iter: this) => M,
    context?: mixed
  ): OrderedMap<M, V>;

  mapEntries<KM, VM>(
    mapper: (entry: [K, V], index: number, iter: this) => [KM, VM],
    context?: mixed
  ): OrderedMap<KM, VM>;

  flatMap<KM, VM>(
    mapper: (value: V, key: K, iter: this) => Iterable<[KM, VM]>,
    context?: mixed
  ): OrderedMap<KM, VM>;

  flatten(depth?: number): OrderedMap<any, any>;
  flatten(shallow?: boolean): OrderedMap<any, any>;
}

declare function isSet(maybeSet: mixed): boolean %checks(maybeSet instanceof Set);
declare class Set<+T> extends SetCollection<T> {
  static <T>(collection?: Iterable<T>): Set<T>;

  static of<T>(...values: T[]): Set<T>;
  static fromKeys<T>(iter: Iterable<[T, mixed]>): Set<T>;
  static fromKeys<K, V>(object: { [key: K]: V }): Set<K>;

  static intersect(sets: Iterable<Iterable<T>>): Set<T>;
  static union(sets: Iterable<Iterable<T>>): Set<T>;

  static isSet: typeof isSet;

  size: number;

  add<U>(value: U): Set<T | U>;
  delete(value: T): this;
  remove(value: T): this;
  clear(): this;
  union<U>(...collections: Iterable<U>[]): Set<T | U>;
  merge<U>(...collections: Iterable<U>[]): Set<T | U>;
  intersect<U>(...collections: Iterable<U>[]): Set<T & U>;
  subtract(...collections: Iterable<mixed>[]): this;

  withMutations(mutator: (mutable: this) => mixed): this;
  asMutable(): this;
  asImmutable(): this;

  // Override specialized return types

  concat<C>(...iters: Array<Iterable<C> | C>): Set<T | C>;

  map<M>(
    mapper: (value: T, value: T, iter: this) => M,
    context?: mixed
  ): Set<M>;

  flatMap<M>(
    mapper: (value: T, value: T, iter: this) => Iterable<M>,
    context?: mixed
  ): Set<M>;

  flatten(depth?: number): Set<any>;
  flatten(shallow?: boolean): Set<any>;
}

// Overrides except for `isOrderedSet` are for specialized return types
declare function isOrderedSet(maybeOrderedSet: mixed): boolean %checks(maybeOrderedSet instanceof OrderedSet);
declare class OrderedSet<+T> extends Set<T> {
  static <T>(collection: Iterable<T>): OrderedSet<T>;
  static (_: void): OrderedSet<any>;

  static of<T>(...values: T[]): OrderedSet<T>;
  static fromKeys<T>(iter: Iterable<[T, mixed]>): OrderedSet<T>;
  static fromKeys<K, V>(object: { [key: K]: V }): OrderedSet<K>;

  static isOrderedSet: typeof isOrderedSet;

  size: number;

  add<U>(value: U): OrderedSet<T | U>;
  union<U>(...collections: Iterable<U>[]): OrderedSet<T | U>;
  merge<U>(...collections: Iterable<U>[]): OrderedSet<T | U>;
  intersect<U>(...collections: Iterable<U>[]): OrderedSet<T & U>;

  concat<C>(...iters: Array<Iterable<C> | C>): OrderedSet<T | C>;

  map<M>(
    mapper: (value: T, value: T, iter: this) => M,
    context?: mixed
  ): OrderedSet<M>;

  flatMap<M>(
    mapper: (value: T, value: T, iter: this) => Iterable<M>,
    context?: mixed
  ): OrderedSet<M>;

  flatten(depth?: number): OrderedSet<any>;
  flatten(shallow?: boolean): OrderedSet<any>;

  zip<A>(
    a: Iterable<A>,
    ..._: []
  ): OrderedSet<[T, A]>;
  zip<A, B>(
    a: Iterable<A>,
    b: Iterable<B>,
    ..._: []
  ): OrderedSet<[T, A, B]>;
  zip<A, B, C>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    ..._: []
  ): OrderedSet<[T, A, B, C]>;
  zip<A, B, C, D>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    ..._: []
  ): OrderedSet<[T, A, B, C, D]>;
  zip<A, B, C, D, E>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    e: Iterable<E>,
    ..._: []
  ): OrderedSet<[T, A, B, C, D, E]>;

  zipWith<A, R>(
    zipper: (value: T, a: A) => R,
    a: Iterable<A>,
    ..._: []
  ): OrderedSet<R>;
  zipWith<A, B, R>(
    zipper: (value: T, a: A, b: B) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    ..._: []
  ): OrderedSet<R>;
  zipWith<A, B, C, R>(
    zipper: (value: T, a: A, b: B, c: C) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    ..._: []
  ): OrderedSet<R>;
  zipWith<A, B, C, D, R>(
    zipper: (value: T, a: A, b: B, c: C, d: D) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    ..._: []
  ): OrderedSet<R>;
  zipWith<A, B, C, D, E, R>(
    zipper: (value: T, a: A, b: B, c: C, d: D, e: E) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    e: Iterable<E>,
    ..._: []
  ): OrderedSet<R>;
}

declare function isStack(maybeStack: mixed): boolean %checks(maybeStack instanceof Stack);
declare class Stack<+T> extends IndexedCollection<T> {
  static <T>(collection?: Iterable<T>): Stack<T>;

  static isStack(maybeStack: mixed): boolean;
  static of<T>(...values: T[]): Stack<T>;

  static isStack: typeof isStack;

  size: number;

  peek(): T;
  clear(): this;
  unshift<U>(...values: U[]): Stack<T | U>;
  unshiftAll<U>(iter: Iterable<U>): Stack<T | U>;
  shift(): this;
  push<U>(...values: U[]): Stack<T | U>;
  pushAll<U>(iter: Iterable<U>): Stack<T | U>;
  pop(): this;

  withMutations(mutator: (mutable: this) => mixed): this;
  asMutable(): this;
  asImmutable(): this;

  // Override specialized return types

  concat<C>(...iters: Array<Iterable<C> | C>): Stack<T | C>;

  map<M>(
    mapper: (value: T, index: number, iter: this) => M,
    context?: mixed
  ): Stack<M>;

  flatMap<M>(
    mapper: (value: T, index: number, iter: this) => Iterable<M>,
    context?: mixed
  ): Stack<M>;

  flatten(depth?: number): Stack<any>;
  flatten(shallow?: boolean): Stack<any>;

  zip<A>(
    a: Iterable<A>,
    ..._: []
  ): Stack<[T, A]>;
  zip<A, B>(
    a: Iterable<A>,
    b: Iterable<B>,
    ..._: []
  ): Stack<[T, A, B]>;
  zip<A, B, C>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    ..._: []
  ): Stack<[T, A, B, C]>;
  zip<A, B, C, D>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    ..._: []
  ): Stack<[T, A, B, C, D]>;
  zip<A, B, C, D, E>(
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    e: Iterable<E>,
    ..._: []
  ): Stack<[T, A, B, C, D, E]>;

  zipWith<A, R>(
    zipper: (value: T, a: A) => R,
    a: Iterable<A>,
    ..._: []
  ): Stack<R>;
  zipWith<A, B, R>(
    zipper: (value: T, a: A, b: B) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    ..._: []
  ): Stack<R>;
  zipWith<A, B, C, R>(
    zipper: (value: T, a: A, b: B, c: C) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    ..._: []
  ): Stack<R>;
  zipWith<A, B, C, D, R>(
    zipper: (value: T, a: A, b: B, c: C, d: D) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    ..._: []
  ): Stack<R>;
  zipWith<A, B, C, D, E, R>(
    zipper: (value: T, a: A, b: B, c: C, d: D, e: E) => R,
    a: Iterable<A>,
    b: Iterable<B>,
    c: Iterable<C>,
    d: Iterable<D>,
    e: Iterable<E>,
    ..._: []
  ): Stack<R>;
}

declare function Range(start?: number, end?: number, step?: number): IndexedSeq<number>;
declare function Repeat<T>(value: T, times?: number): IndexedSeq<T>;

declare function isRecord(maybeRecord: any): boolean %checks(maybeRecord instanceof RecordInstance);
declare class Record {
  static <Values>(spec: Values, name?: string): RecordClass<Values>;
  constructor<Values>(spec: Values, name?: string): RecordClass<Values>;

  static isRecord: typeof isRecord;

  static getDescriptiveName(record: RecordInstance<*>): string;
}

declare interface RecordClass<T> {
  (values: $Shape<T> | Iterable<[string, any]>): RecordInstance<T> & T;
  new (values: $Shape<T> | Iterable<[string, any]>): RecordInstance<T> & T;
}

declare class RecordInstance<T: Object> {
  size: number;

  has(key: string): boolean;
  get<K: $Keys<T>>(key: K): /*T[K]*/any;

  equals(other: any): boolean;
  hashCode(): number;

  set<K: $Keys<T>>(key: K, value: /*T[K]*/any): this;
  update<K: $Keys<T>>(key: K, updater: (value: /*T[K]*/any) => /*T[K]*/any): this;
  merge(...collections: Array<$Shape<T> | Iterable<[string, any]>>): this;
  mergeDeep(...collections: Array<$Shape<T> | Iterable<[string, any]>>): this;

  mergeWith(
    merger: (oldVal: any, newVal: any, key: $Keys<T>) => any,
    ...collections: Array<$Shape<T> | Iterable<[string, any]>>
  ): this;
  mergeDeepWith(
    merger: (oldVal: any, newVal: any, key: any) => any,
    ...collections: Array<$Shape<T> | Iterable<[string, any]>>
  ): this;

  delete<K: $Keys<T>>(key: K): this;
  remove<K: $Keys<T>>(key: K): this;
  clear(): this;

  setIn(keyPath: Iterable<any>, value: any): this;
  updateIn(keyPath: Iterable<any>, updater: (value: any) => any): this;
  mergeIn(keyPath: Iterable<any>, ...collections: Array<any>): this;
  mergeDeepIn(keyPath: Iterable<any>, ...collections: Array<any>): this;
  deleteIn(keyPath: Iterable<any>): this;
  removeIn(keyPath: Iterable<any>): this;

  toSeq(): KeyedSeq<$Keys<T>, any>;

  toJS(): { [key: $Keys<T>]: mixed };
  toJSON(): T;
  toObject(): T;

  withMutations(mutator: (mutable: this) => mixed): this;
  asMutable(): this;
  asImmutable(): this;

  @@iterator(): Iterator<[$Keys<T>, any]>;
}

declare function fromJS(
  jsValue: mixed,
  reviver?: (
    key: string | number,
    sequence: KeyedCollection<string, mixed> | IndexedCollection<mixed>,
    path?: Array<string | number>
  ) => mixed
): mixed;

declare function is(first: mixed, second: mixed): boolean;
declare function hash(value: mixed): number;

export {
  Collection,
  Seq,

  List,
  Map,
  OrderedMap,
  OrderedSet,
  Range,
  Repeat,
  Record,
  Set,
  Stack,

  fromJS,
  is,
  hash,

  isImmutable,
  isCollection,
  isKeyed,
  isIndexed,
  isAssociative,
  isOrdered,
  isRecord,
  isValueObject,
}

export default {
  Collection,
  Seq,

  List,
  Map,
  OrderedMap,
  OrderedSet,
  Range,
  Repeat,
  Record,
  Set,
  Stack,

  fromJS,
  is,
  hash,

  isImmutable,
  isCollection,
  isKeyed,
  isIndexed,
  isAssociative,
  isOrdered,
  isRecord,
  isValueObject,
}

export type {
  KeyedCollection,
  IndexedCollection,
  SetCollection,
  KeyedSeq,
  IndexedSeq,
  SetSeq,
  ValueObject,
}
