import { getOnlyFulfilled, waitForAllPromises, waitForFirstFulfilledPromise } from './promise-helpers';
const flatten = arr => [].concat(...arr);

/**
 * Allow to run methods from the given provider interface across all providers seamlessly.
 * Handles promise racing and discards rejected promises safely.
 */
export default (providers => {
  if (providers.length === 0) {
    throw new Error('At least one provider must be provided');
  }
  const getFulfilledProviders = async () => {
    const results = await waitForAllPromises(providers.map(result => Promise.resolve(result)));

    // Filter out null/undefined providers to prevent errors when calling methods on them
    return getOnlyFulfilled(results).filter(provider => provider != null);
  };
  const runInAllProviders = async mapFunction => {
    return (await getFulfilledProviders()).map(provider => mapFunction(provider));
  };

  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const createCallback = (methodName, args) => provider => {
    const method = provider[methodName];
    if (typeof method === 'function') {
      return method.apply(provider, args);
    }
    throw new Error(`"${String(methodName)}" isn't a function of the provider`);
  };

  /**
   * Run a method from the provider which expects to return a single item
   * @param methodName
   * @param args
   */
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const invokeSingle = async (methodName, args) => {
    const callback = createCallback(methodName, args);
    return waitForFirstFulfilledPromise(await runInAllProviders(callback));
  };

  /**
   * Run a method in the provider which expectes to return a list of items
   * @param methodName
   * @param args
   */
  // Ignored via go/ees005
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  const invokeList = async (methodName, args) => {
    const callback = createCallback(methodName, args);
    const results = await waitForAllPromises(await runInAllProviders(callback));
    const fulfilledResults = getOnlyFulfilled(results);
    return flatten(fulfilledResults).filter(result => result);
  };
  return {
    invokeSingle,
    invokeList
  };
});