export function sequence<T, R> (
  tasks: T[],
  fn: (task: T) => R
) {
  return tasks.reduce(
    (promise, task): any => promise.then(() => fn(task)),
    Promise.resolve()
  )
}

export function parallel<T, R> (
  tasks: T[],
  fn: (task: T) => R
) {
  return Promise.all(tasks.map(fn))
}

export function chainFn<Fn> (base: Fn, fn: Fn): Fn {
  if (typeof fn !== 'function') {
    return base
  }
  return function (this: any, ...args: any[]) {
    if (typeof base !== 'function') {
      return fn.apply(this, args)
    }
    let baseResult = base.apply(this, args)
    // Allow function to mutate the first argument instead of returning the result
    if (baseResult === undefined) {
      [baseResult] = args
    }
    const fnResult = fn.call(
      this,
      baseResult,
      ...Array.prototype.slice.call(args, 1)
    )
    // Return mutated argument if no result was returned
    if (fnResult === undefined) {
      return baseResult
    }
    return fnResult
  } as unknown as Fn
}
