/** @module fp **/

/** Reducer function used by `pipe` and `compose` functions to compose sync and async functions
 * @argument {function|Promise} chain a chain of functions or promises
 * @argument {function|Promise} func a new function or promise to add to the chain
 * @method
 */
const mixCompose = (chain, func) => (chain instanceof Promise || typeof chain.then === 'function' ? chain.then(func) : func(chain));

/** Compose regular functions or promises generating a final function.
 * - Compose works from left to right.
 * - If you compose one single promise the final result will be a promise too.
 * - You can only compose functions with the same arity.
 * @argument {function|Promise} arguments N number of functions or promises.
 * @returns {function|Promise} function or Promise that execute the all composed ones.
 * @example
 * const sum3 = a => a+3
 * const mult2 = a => a*2
 * const sum2Async = a => Promise.resolve(a+2) // Simulate async response
 *
 * const sumAndMult = pipe(sum3,mult2);
 * sumAndMult(1) // -> (1+3)*2 = 8
 *
 * const sumAndMultAsync = pipe(sum3,mult2,sum2Async);
 * await sumAndMultAsync(1) // -> ((1+3)*2)+2 = 10
 * @method
 */
const pipe = (...fn) => input => fn.reduce(mixCompose, input);

/** Compose regular functions or promises generating a final function.
 * - Compose works from right to left.
 * - If you compose one single promise the final result will be a promise too.
 * - You can only compose functions with the same arity.
 * @argument {function|Promise} arguments N number of functions or promises.
 * @returns {function|Promise} function or Promise that execute the all composed ones.
 * @example
 * const sum3 = a => a+3
 * const mult2 = a => a*2
 * const sum2Async = a => Promise.resolve(a+2) // Simulate async response
 *
 * const sumAndMult = compose(sum3,mult2);
 * sumAndMult(1) // -> (1*2)+3 = 5
 *
 * const sumAndMultAsync = compose(sum3,mult2,sum2Async);
 * await sumAndMultAsync(1) // -> ((1+2)*2)+3 = 9
 * @method
 */
const compose = (...fn) => input => fn.reduceRight(mixCompose, input);

/** Currify any function allowing the partial application of its arguments
 * @argument {function} function function with at least two arguments
 * @returns {function} curried function.
 * @example
 * const sum = curry((a,b) = a+b);
 * const sum3 = sum(3);
 * sum3(3) // -> 6
 * sum(3,3) // -> 6
 * sum(3)(3) // -> 6
 * @method
 */
const curry = f => {
  if (typeof f !== 'function') {
    throw new Error(`curry requires a function, [${typeof f}] passed`);
  }
  return function currify(...arg) {
    const args = Array.prototype.slice.call(arg);
    return args.length >= f.length ? f(...args) : currify.bind(null, ...args);
  };
};
module.exports = { pipe, compose, curry };