// @flow

// see https://gist.github.com/thecotne/6e5969f4aaf8f253985ed36b30ac9fe0
type $FlowGen$If<X: boolean, Then, Else = empty> = $Call<
  ((true, Then, Else) => Then) & ((false, Then, Else) => Else),
  X,
  Then,
  Else
>;

type $FlowGen$Assignable<A, B> = $Call<
  ((...r: [B]) => true) & ((...r: [A]) => false),
  A
>;

declare var TalkbackKind: {|
  +Pull: 0, // 0
  +Close: 1, // 1
|};
declare type TalkbackFn = (signal: $Values<typeof TalkbackKind>) => void;
declare type TeardownFn = () => void;
declare var SignalKind: {|
  +Start: 0, // 0
  +Push: 1, // 1
  +End: 0, // 0
|};
declare interface Tag<T> {
  tag: T;
}
declare type Start<_T> = { ...Tag<typeof SignalKind.Start>, ...[TalkbackFn] };
declare type Push<T> = { ...Tag<typeof SignalKind.Push>, ...[T] };
declare type Signal<T> = Start<T> | Push<T> | typeof SignalKind.End;
declare type Sink<T> = (signal: Signal<T>) => void;
declare type Source<T> = (sink: Sink<T>) => void;
declare type Operator<In, Out> = (a: Source<In>) => Source<Out>;
declare type TypeOfSource<T> = $FlowGen$If<
  $FlowGen$Assignable<T, Source<U>>,
  U,
  empty
>;
declare interface Subscription {
  unsubscribe(): void;
}
declare interface Observer<T> {
  next(value: T): void;
  complete(): void;
}
declare type Subject<T> = {
  source: Source<T>,
  ...
} & Observer<T>;
declare type SourceIterable<T> = { ... } & AsyncIterator & AsyncIterable;
declare function lazy<T>(produce: () => Source<T>): Source<T>;
declare function fromAsyncIterable<T>(
  iterable: AsyncIterable<T> | AsyncIterator<T>
): Source<T>;
declare function fromIterable<T>(
  iterable: Iterable<T> | AsyncIterable<T>
): Source<T>;
declare var fromArray: <T>(array: T[]) => Source<T>;
declare function fromValue<T>(value: T): Source<T>;
declare function make<T>(
  subscriber: (observer: Observer<T>) => TeardownFn
): Source<T>;
declare function makeSubject<T>(): Subject<T>;
declare var empty: Source<any>;
declare var never: Source<any>;
declare function interval(ms: number): Source<number>;
declare function fromDomEvent(
  element: HTMLElement,
  event: string
): Source<Event>;
declare function fromPromise<T>(promise: Promise<T>): Source<T>;
declare function buffer<S, T>(notifier: Source<S>): Operator<T, T[]>;
declare function concatMap<In, Out>(
  map: (value: In) => Source<Out>
): Operator<In, Out>;
declare function concatAll<T>(source: Source<Source<T>>): Source<T>;
declare function concat<T>(sources: Source<T>[]): Source<T>;
declare function filter<In, Out: In>(
  predicate: (value: In) => boolean
): Operator<In, Out>;
declare function filter<T>(predicate: (value: T) => boolean): Operator<T, T>;
declare function map<In, Out>(map: (value: In) => Out): Operator<In, Out>;
declare function mergeMap<In, Out>(
  map: (value: In) => Source<Out>
): Operator<In, Out>;
declare function mergeAll<T>(source: Source<Source<T>>): Source<T>;
declare function merge<T>(sources: Source<T>[]): Source<T>;
declare function onEnd<T>(callback: () => void): Operator<T, T>;
declare function onPush<T>(callback: (value: T) => void): Operator<T, T>;
declare function onStart<T>(callback: () => void): Operator<T, T>;
declare function sample<S, T>(notifier: Source<S>): Operator<T, T>;
declare function scan<In, Out>(
  reducer: (acc: Out, value: In) => Out,
  seed: Out
): Operator<In, Out>;
declare function share<T>(source: Source<T>): Source<T>;
declare function skip<T>(wait: number): Operator<T, T>;
declare function skipUntil<S, T>(notifier: Source<S>): Operator<T, T>;
declare function skipWhile<T>(predicate: (value: T) => boolean): Operator<T, T>;
declare function switchMap<In, Out>(
  map: (value: In) => Source<Out>
): Operator<In, Out>;
declare function switchAll<T>(source: Source<Source<T>>): Source<T>;
declare function take<T>(max: number): Operator<T, T>;
declare function takeLast<T>(max: number): Operator<T, T>;
declare function takeUntil<S, T>(notifier: Source<S>): Operator<T, T>;
declare function takeWhile<T>(
  predicate: (value: T) => boolean,
  addOne?: boolean
): Operator<T, T>;
declare function debounce<T>(timing: (value: T) => number): Operator<T, T>;
declare function delay<T>(wait: number): Operator<T, T>;
declare function throttle<T>(timing: (value: T) => number): Operator<T, T>;
declare function subscribe<T>(
  subscriber: (value: T) => void
): (source: Source<T>) => Subscription;
declare function forEach<T>(
  subscriber: (value: T) => void
): (source: Source<T>) => void;
declare function publish<T>(source: Source<T>): void;
declare var toAsyncIterable: <T>(source: Source<T>) => SourceIterable<T>;
declare function toArray<T>(source: Source<T>): T[];
declare function toPromise<T>(source: Source<T>): Promise<T>;
declare type TypeOfSourceArray<T: [] & any[]> = $FlowGen$If<
  $FlowGen$Assignable<T, [Head] & Tail>,
  [TypeOfSource<Head>] & TypeOfSourceArray<Tail>,
  []
>;
declare interface zip {
  <Sources: [] & Source<any>[]>(
    sources: [] & Sources
  ): Source<TypeOfSourceArray<Sources>>;
  <
    Sources: {
      [prop: string]: Source<any>,
    }
  >(
    sources: Sources
  ): Source<
    $ObjMapi<
      Sources,
      <Property>(Property) => TypeOfSource<$ElementType<Sources, Property>>
    >
  >;
}
declare function zip<T>(
  sources: Source<T>[] | { [key: string]: Source<T>, ... }
): Source<T[] | { [key: string]: T, ... }>;
declare function combine<Sources: Source<any>[]>(
  ...sources: Sources
): Source<TypeOfSourceArray<Sources>>;

declare module "global" {
  declare interface SymbolConstructor {
    +observable: Symbol;
  }
}

declare interface ObservableSubscription {
  closed: boolean;
  unsubscribe(): void;
}
declare interface ObservableObserver<T> {
  next(value: T): void;
  error?: (error: any) => void;
  complete?: () => void;
}
declare interface ObservableLike<T> {
  subscribe(observer: ObservableObserver<T>): {
    unsubscribe(): void,
    ...
  };
}
declare interface Observable<T> {
  subscribe(observer: ObservableObserver<T>): ObservableSubscription;
  subscribe(
    onNext: (value: T) => any,
    onError?: (error: any) => any,
    onComplete?: () => any
  ): ObservableSubscription;
}
declare function fromObservable<T>(input: ObservableLike<T>): Source<T>;
declare function toObservable<T>(source: Source<T>): Observable<T>;
declare interface Callbag<I, O> {
  (t: 0, d: Callbag<O, I>): void;
  (t: 1, d: I): void;
  (t: 2, d?: any): void;
}
declare function fromCallbag<T>(callbag: Callbag<any, T>): Source<T>;
declare function toCallbag<T>(source: Source<T>): Callbag<any, T>;
declare interface UnaryFn<T, R> {
  (source: T): R;
}
declare interface pipe {
  <T, A>(source: Source<T>, op1: UnaryFn<Source<T>, Source<A>>): Source<A>;
  <T, A, B>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>
  ): Source<B>;
  <T, A, B, C>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>,
    op3: UnaryFn<Source<B>, Source<C>>
  ): Source<C>;
  <T, A, B, C, D>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>,
    op3: UnaryFn<Source<B>, Source<C>>,
    op4: UnaryFn<Source<C>, Source<D>>
  ): Source<D>;
  <T, A, B, C, D, E>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>,
    op3: UnaryFn<Source<B>, Source<C>>,
    op4: UnaryFn<Source<C>, Source<D>>,
    op5: UnaryFn<Source<D>, Source<E>>
  ): Source<E>;
  <T, A, B, C, D, E, F>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>,
    op3: UnaryFn<Source<B>, Source<C>>,
    op4: UnaryFn<Source<C>, Source<D>>,
    op5: UnaryFn<Source<D>, Source<E>>,
    op6: UnaryFn<Source<E>, Source<F>>
  ): Source<F>;
  <T, A, B, C, D, E, F, G>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>,
    op3: UnaryFn<Source<B>, Source<C>>,
    op4: UnaryFn<Source<C>, Source<D>>,
    op5: UnaryFn<Source<D>, Source<E>>,
    op6: UnaryFn<Source<E>, Source<F>>,
    op7: UnaryFn<Source<F>, Source<G>>
  ): Source<G>;
  <T, A, B, C, D, E, F, G, H>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>,
    op3: UnaryFn<Source<B>, Source<C>>,
    op4: UnaryFn<Source<C>, Source<D>>,
    op5: UnaryFn<Source<D>, Source<E>>,
    op6: UnaryFn<Source<E>, Source<F>>,
    op7: UnaryFn<Source<F>, Source<G>>,
    op8: UnaryFn<Source<G>, Source<H>>
  ): Source<H>;
  <T, R>(source: Source<T>, consumer: UnaryFn<Source<T>, R>): R;
  <T, A, R>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    consumer: UnaryFn<Source<A>, R>
  ): R;
  <T, A, B, R>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>,
    consumer: UnaryFn<Source<B>, R>
  ): R;
  <T, A, B, C, R>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>,
    op3: UnaryFn<Source<B>, Source<C>>,
    consumer: UnaryFn<Source<C>, R>
  ): R;
  <T, A, B, C, D, R>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>,
    op3: UnaryFn<Source<B>, Source<C>>,
    op4: UnaryFn<Source<C>, Source<D>>,
    consumer: UnaryFn<Source<D>, R>
  ): R;
  <T, A, B, C, D, E, R>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>,
    op3: UnaryFn<Source<B>, Source<C>>,
    op4: UnaryFn<Source<C>, Source<D>>,
    op5: UnaryFn<Source<D>, Source<E>>,
    consumer: UnaryFn<Source<E>, R>
  ): R;
  <T, A, B, C, D, E, F, R>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>,
    op3: UnaryFn<Source<B>, Source<C>>,
    op4: UnaryFn<Source<C>, Source<D>>,
    op5: UnaryFn<Source<D>, Source<E>>,
    op6: UnaryFn<Source<E>, Source<F>>,
    consumer: UnaryFn<Source<F>, R>
  ): R;
  <T, A, B, C, D, E, F, G, R>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>,
    op3: UnaryFn<Source<B>, Source<C>>,
    op4: UnaryFn<Source<C>, Source<D>>,
    op5: UnaryFn<Source<D>, Source<E>>,
    op6: UnaryFn<Source<E>, Source<F>>,
    op7: UnaryFn<Source<F>, Source<G>>,
    consumer: UnaryFn<Source<G>, R>
  ): R;
  <T, A, B, C, D, E, F, G, H, R>(
    source: Source<T>,
    op1: UnaryFn<Source<T>, Source<A>>,
    op2: UnaryFn<Source<A>, Source<B>>,
    op3: UnaryFn<Source<B>, Source<C>>,
    op4: UnaryFn<Source<C>, Source<D>>,
    op5: UnaryFn<Source<D>, Source<E>>,
    op6: UnaryFn<Source<E>, Source<F>>,
    op7: UnaryFn<Source<F>, Source<G>>,
    op8: UnaryFn<Source<G>, Source<H>>,
    consumer: UnaryFn<Source<H>, R>
  ): R;
}
declare var pipe: pipe;
declare export {
  Observer,
  Operator,
  Signal,
  Sink,
  Source,
  Subject,
  Subscription,
  TeardownFn,
  TypeOfSource,
  buffer,
  combine,
  concat,
  concatAll,
  concatMap,
  debounce,
  delay,
  empty,
  filter,
  mergeAll as flatten,
  forEach,
  fromArray,
  fromAsyncIterable,
  fromCallbag,
  fromDomEvent,
  fromIterable,
  fromObservable,
  fromPromise,
  fromValue,
  interval,
  lazy,
  make,
  makeSubject,
  map,
  merge,
  mergeAll,
  mergeMap,
  never,
  onEnd,
  onPush,
  onStart,
  pipe,
  publish,
  sample,
  scan,
  share,
  skip,
  skipUntil,
  skipWhile,
  subscribe,
  switchAll,
  switchMap,
  take,
  takeLast,
  takeUntil,
  takeWhile,
  onPush as tap,
  throttle,
  toArray,
  toAsyncIterable,
  toCallbag,
  toObservable,
  toPromise,
  zip,
};
