'use strict';

const pluralize = require('pluralize');

const { getWordsList, flatten } = require('../../utilities');
const { throwError } = require('../../error');
const { getLimits } = require('../../limits');

const { getColl } = require('./get_coll');

// Validate request limits
const validateRequestLimits = function ({ runOpts, mInput }) {
  const limits = getLimits({ runOpts });

  validators.forEach(validator => validator({ ...mInput, limits }));
};

const validateMaxActions = function ({ limits: { maxActions }, actions }) {
  if (actions.length <= maxActions) { return; }

  const message = `The request must contain less than ${maxActions - 1} nested commands, but there are ${actions.length - 1} of them`;
  throwError(message, { reason: 'REQUEST_LIMIT' });
};

// Nested patch|create|upsert commands use `maxmodels` instead
// Nested delete commands are not limited, as they are meant not to be performed
// several times
const validateNestedFind = function ({ limits, actions, top, schema }) {
  if (top.command.type !== 'find') { return; }

  const tooNestedActions = actions
    .filter(action => isTooNestedFind({ action, schema, top, limits }));
  if (tooNestedActions.length === 0) { return; }

  const paths = tooNestedActions
    .map(({ commandpath }) => commandpath.slice(1).join('.'));
  const pathsA = getWordsList(paths, { op: 'and', quotes: true });
  const message = `The following ${pluralize('command', paths.length)} ${pluralize('is', paths.length)} nested too deeply: ${pathsA}. 'find' commands can only target collections at the top level or the second level.`;
  throwError(message, { reason: 'REQUEST_LIMIT' });
};

const isTooNestedFind = function ({
  action: { commandpath },
  schema: { shortcuts: { collsMap } },
  top,
  limits: { maxFindManyDepth },
}) {
  if (commandpath.length <= maxFindManyDepth) { return false; }

  const { multiple } = getColl({ commandpath, collsMap, top });
  return multiple;
};

// Validate `args.data` against `maxmodels` limit
const validateMaxData = function ({
  actions,
  limits: { maxmodels },
  top: { command },
}) {
  // Not applied to:
  //  - find|patch commands: applied later since response size is not known yet
  //  - delete commands: they are never limited
  if (!MAX_DATA_COMMANDS.includes(command.type)) { return; }

  const dataA = actions.map(({ args: { data } }) => data);
  const dataB = flatten(dataA);

  if (dataB.length <= maxmodels) { return; }

  const message = `The 'data' argument must not contain more than ${maxmodels} models, but it contains ${dataB.length} models`;
  throwError(message, { reason: 'REQUEST_LIMIT' });
};

const MAX_DATA_COMMANDS = ['create', 'upsert'];

const validators = [
  validateMaxActions,
  validateNestedFind,
  validateMaxData,
];

module.exports = {
  validateRequestLimits,
};

@@ -2560,16 +2560,41 @@
 .length%7D
+ models, including nested
  models%60

Change error message