'use strict';

const { getStringIfConstant } = require('eslint-utils');

const DEFAULT_TEST_NAMES = [ 'describe', 'context', 'suite', 'it', 'test', 'specify' ];
const ERROR_MESSAGE = 'Unexpected empty test description.';

function objectOptions(options = {}) {
    const {
        testNames = DEFAULT_TEST_NAMES,
        message
    } = options;

    return { testNames, message };
}

function isLiteral(node) {
    return node && node.type === 'Literal';
}

function isStaticallyAnalyzableDescription(node, extractedText) {
    if (extractedText === null) {
        return isLiteral(node);
    }

    return true;
}

function isValidDescriptionArgumentNode(node) {
    if (!node) {
        return false;
    }

    return [
        'Literal', 'TemplateLiteral', 'TaggedTemplateExpression',
        'Identifier', 'MemberExpression', 'CallExpression',
        'LogicalExpression', 'BinaryExpression', 'ConditionalExpression',
        'UnaryExpression', 'SpreadElement', 'AwaitExpression',
        'YieldExpression', 'NewExpression' ].includes(node.type);
}

module.exports = {
    meta: {
        type: 'suggestion',
        docs: {
            description: 'Disallow empty test descriptions',
            url: 'https://github.com/lo1tuma/eslint-plugin-mocha/blob/master/docs/rules/no-empty-description.md'
        },
        schema: [
            {
                type: 'object',
                properties: {
                    testNames: {
                        type: 'array',
                        items: {
                            type: 'string'
                        }
                    },
                    message: {
                        type: 'string'
                    }
                },
                additionalProperties: false
            }
        ]
    },
    create(context) {
        const options = context.options[0];
        const { sourceCode = {} } = context;

        const { testNames, message } = objectOptions(options);

        function isTest(node) {
            return node.callee && node.callee.name && testNames.includes(node.callee.name);
        }

        // eslint-disable-next-line complexity
        function isNonEmptyDescription(mochaCallExpression) {
            const description = mochaCallExpression.arguments[0];

            if (!isValidDescriptionArgumentNode(description)) {
                return false;
            }

            const scope = typeof sourceCode.getScope !== 'undefined' ?
                sourceCode.getScope(mochaCallExpression) :
                context.getScope();

            const text = getStringIfConstant(description, scope);

            if (!isStaticallyAnalyzableDescription(description, text)) {
                return true;
            }

            return text && text.trim().length;
        }

        return {
            CallExpression(node) {
                if (isTest(node)) {
                    if (!isNonEmptyDescription(node)) {
                        context.report({
                            node,
                            message: message || ERROR_MESSAGE
                        });
                    }
                }
            }
        };
    }
};
