'use strict';

const { find } = require('rambda');
const createAstUtils = require('../util/ast');

module.exports = {
    meta: {
        type: 'problem',
        docs: {
            description: 'Enforces handling of callbacks for async tests',
            url: 'https://github.com/lo1tuma/eslint-plugin-mocha/blob/master/docs/rules/handle-done-callback.md'
        },
        schema: [
            {
                type: 'object',
                properties: {
                    ignoreSkipped: {
                        type: 'boolean',
                        default: false
                    }
                },
                additionalProperties: false
            }
        ]
    },
    // eslint-disable-next-line max-statements
    create(context) {
        const astUtils = createAstUtils(context.settings);
        const { sourceCode = {} } = context;
        const [ { ignoreSkipped = false } = {} ] = context.options;
        const modifiersToCheck = ignoreSkipped ? [ 'only' ] : [ 'only', 'skip' ];

        function isAsyncFunction(functionExpression) {
            return functionExpression.params.length === 1;
        }

        function findParamInScope(paramName, scope) {
            return find(function (variable) {
                return variable.name === paramName && variable.defs[0].type === 'Parameter';
            }, scope.variables);
        }

        function isReferenceHandled(reference) {
            const parent = context.getSourceCode().getNodeByRangeIndex(reference.identifier.range[0]).parent;

            return parent.type === 'CallExpression';
        }

        function hasHandledReferences(references) {
            return references.some(isReferenceHandled);
        }

        function checkAsyncMochaFunction(functionExpression) {
            const scope = typeof sourceCode.getScope !== 'undefined' ?
                sourceCode.getScope(functionExpression) :
                context.getScope();

            const callback = functionExpression.params[0];
            const callbackName = callback.name;
            const callbackVariable = findParamInScope(callbackName, scope);

            if (callbackVariable && !hasHandledReferences(callbackVariable.references)) {
                context.report({
                    node: callback,
                    message: 'Expected "{{name}}" callback to be handled.', data: { name: callbackName }
                });
            }
        }

        function check(node) {
            if (astUtils.hasParentMochaFunctionCall(node, { modifiers: modifiersToCheck }) && isAsyncFunction(node)) {
                checkAsyncMochaFunction(node);
            }
        }

        return {
            FunctionExpression: check,
            ArrowFunctionExpression: check
        };
    }
};
