'use strict'

module.exports = {
  meta: {
    docs: {
      description: 'Prevent waiting for arbitrary time periods',
      category: 'Possible Errors',
      recommended: true,
      url: 'https://on.cypress.io/best-practices#Unnecessary-Waiting',
    },
    schema: [],
    messages: {
      unexpected: 'Do not wait for arbitrary time periods',
    },
  },
  create (context) {
    return {
      CallExpression (node) {
        if (isCallingCyWait(node)) {
          const scope = context.getScope()

          if (isIdentifierNumberConstArgument(node, scope) || isNumberArgument(node)) {
            context.report({ node, messageId: 'unexpected' })
          }
        }
      },
    }
  },
}

function nodeIsCalledByCy (node) {
  if (node.type === 'Identifier' && node.name === 'cy') return true

  if (typeof node.callee === 'undefined' || typeof node.callee.object === 'undefined') {
    return false
  }

  return nodeIsCalledByCy(node.callee.object)
}

function isCallingCyWait (node) {
  return node.callee.type === 'MemberExpression' &&
         nodeIsCalledByCy(node) &&
         node.callee.property.type === 'Identifier' &&
         node.callee.property.name === 'wait'
}

function isNumberArgument (node) {
  return node.arguments.length > 0 &&
         node.arguments[0].type === 'Literal' &&
         typeof (node.arguments[0].value) === 'number'
}

function isIdentifierNumberConstArgument (node, scope) {
  if (node.arguments.length === 0) return false

  if (node.arguments[0].type !== 'Identifier') return false

  const identifier = node.arguments[0]
  const resolvedIdentifier = scope.references.find((ref) => ref.identifier === identifier).resolved
  const definition = resolvedIdentifier.defs[0]
  const isVariable = definition.type === 'Variable'

  // const amount = 1000 or const amount = '@alias'
  // cy.wait(amount)
  if (isVariable) {
    if (!definition.node.init) return false

    return typeof definition.node.init.value === 'number'
  }

  // import { WAIT } from './constants'
  // cy.wait(WAIT)
  // we don't know if WAIT is a number or alias '@someRequest', so don't fail
  if (definition.type === 'ImportBinding') return false

  const param = definition.node.params[definition.index]

  // function wait (amount) { cy.wait(amount) }
  // we can't know the type of value, so don't fail
  if (!param || param.type !== 'AssignmentPattern') return false

  // function wait (amount = 1) { cy.wait(amount) } or
  // function wait (amount = '@alias') { cy.wait(amount) }
  return typeof param.right.value === 'number'
}
