// Return a single method which will chain all of the middleware provided
const combineMiddleware = transforms => {
  let transform;

  const chain = transforms.reduce((stack, go) => {
    return go(args => {
      // Each middleware get's a node and context object. The context allows for
      // nested node to have knowledge of the outer/parent context. All of this is
      // part of args array
      return stack(args, transform);
    });
    // unroll last result
  }, ([id]) => id);

  return (args, topLevelTranfrom) => {
    transform = topLevelTranfrom;
    // kick off the chain of middleware, starting from right to left
    return chain(args, transform);
  };
};

export const combineParsers = sortedParsers => {
  const wildcards = [];

  // Normalize parsers by type
  const parsersByType = sortedParsers.reduce((acc, parser) => {
    Object.entries(parser).forEach(([type, cb]) => {
      // Wildcards may only handle types which have other callbacks attached to
      // them.
      if (type === '*') {
        wildcards.push(cb);
        return;
      }

      if (acc[type] == null) {
        // Prepend any previously defined wildcard to maintain precedence
        acc[type] = [...wildcards];
      }

      acc[type].push(cb);
    });

    return acc;
  }, {});

  return Object.entries(parsersByType).reduce((acc, [key, transforms]) => {
    acc[key] = combineMiddleware(transforms);

    return acc;
  }, {});
};
