'use strict';
const reservedWords = require('reserved-words');
const resolveVariableName = require('./resolve-variable-name');

const indexifyName = (name, index) => name + '_'.repeat(index);

const scopeHasArgumentsSpecial = scope => {
	while (scope) {
		if (scope.taints.get('arguments')) {
			return true;
		}

		scope = scope.upper;
	}

	return false;
};

const someScopeHasVariableName = (name, scopes) => scopes.some(scope => resolveVariableName(name, scope));

const someScopeIsStrict = scopes => scopes.some(scope => scope.isStrict);

const nameCollidesWithArgumentsSpecial = (name, scopes, isStrict) => {
	if (name !== 'arguments') {
		return false;
	}

	return isStrict || scopes.some(scopeHasArgumentsSpecial);
};

const isSafeName = (name, scopes, ecmaVersion, isStrict) => {
	ecmaVersion = Math.min(6, ecmaVersion); // 6 is the latest version understood by `reservedWords`

	return !someScopeHasVariableName(name, scopes) &&
		!reservedWords.check(name, ecmaVersion, isStrict) &&
		!nameCollidesWithArgumentsSpecial(name, scopes, isStrict);
};

const alwaysTrue = () => true;

/**
Rule-specific name check function.

@callback isSafe
@param {string} indexifiedName - The generated candidate name.
@param {Scope[]} scopes - The same list of scopes you pass to `avoidCapture`.
@returns {boolean} - `true` if the `indexifiedName` is ok.
*/

/**
Generates a unique name prefixed with `name` such that:
- it is not defined in any of the `scopes`,
- it is not a reserved word,
- it is not `arguments` in strict scopes (where `arguments` is not allowed),
- it does not collide with the actual `arguments` (which is always defined in function scopes).

Useful when you want to rename a variable (or create a new variable) while being sure not to shadow any other variables in the code.

@param {string} name - The desired name for a new variable.
@param {Scope[]} scopes - The list of scopes the new variable will be referenced in.
@param {number} ecmaVersion - The language version, get it from `context.parserOptions.ecmaVersion`.
@param {isSafe} [isSafe] - Rule-specific name check function.
@returns {string} - Either `name` as is, or a string like `${name}_` suffixed with undescores to make the name unique.
*/
module.exports = (name, scopes, ecmaVersion, isSafe = alwaysTrue) => {
	const isStrict = someScopeIsStrict(scopes);

	let index = 0;
	let indexifiedName = indexifyName(name, index);
	while (!isSafeName(indexifiedName, scopes, ecmaVersion, isStrict) || !isSafe(indexifiedName, scopes)) {
		index++;
		indexifiedName = indexifyName(name, index);
	}

	return indexifiedName;
};
