/**
 * @fileoverview disallow usage of `this` in template.
 * @author Armano
 */
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const utils = require('../utils')
const RESERVED_NAMES = new Set(require('../utils/js-reserved.json'))

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

module.exports = {
  meta: {
    type: 'suggestion',
    docs: {
      description: 'disallow usage of `this` in template',
      category: 'recommended',
      url: 'https://eslint.vuejs.org/rules/this-in-template.html'
    },
    fixable: null,
    schema: [
      {
        enum: ['always', 'never']
      }
    ]
  },

  /**
   * Creates AST event handlers for this-in-template.
   *
   * @param {RuleContext} context - The rule context.
   * @returns {Object} AST event handlers.
   */
  create (context) {
    const options = context.options[0] !== 'always' ? 'never' : 'always'
    let scope = {
      parent: null,
      nodes: []
    }

    return utils.defineTemplateBodyVisitor(context, Object.assign({
      VElement (node) {
        scope = {
          parent: scope,
          nodes: scope.nodes.slice() // make copy
        }
        if (node.variables) {
          for (const variable of node.variables) {
            const varNode = variable.id
            const name = varNode.name
            if (!scope.nodes.some(node => node.name === name)) { // Prevent adding duplicates
              scope.nodes.push(varNode)
            }
          }
        }
      },
      'VElement:exit' (node) {
        scope = scope.parent
      }
    }, options === 'never'
      ? {
        'VExpressionContainer MemberExpression > ThisExpression' (node) {
          const propertyName = utils.getStaticPropertyName(node.parent.property)
          if (!propertyName ||
            scope.nodes.some(el => el.name === propertyName) ||
            RESERVED_NAMES.has(propertyName) || // this.class | this['class']
            /^[0-9].*$|[^a-zA-Z0-9_]/.test(propertyName) // this['0aaaa'] | this['foo-bar bas']
          ) {
            return
          }

          context.report({
            node,
            loc: node.loc,
            message: "Unexpected usage of 'this'."
          })
        }
      }
      : {
        'VExpressionContainer' (node) {
          if (node.references) {
            for (const reference of node.references) {
              if (!scope.nodes.some(el => el.name === reference.id.name)) {
                context.report({
                  node: reference.id,
                  loc: reference.id.loc,
                  message: "Expected 'this'."
                })
              }
            }
          }
        }
      }
    ))
  }
}
