/**
 * @author Pig Fang
 * See LICENSE file in root directory for full license.
 */
'use strict'

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

const utils = require('../utils')

// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------

/**
 * @param {ArrayExpression} node
 * @param {RuleContext} context
 */
function checkArrayExpression(node, context) {
  const booleanType = node.elements.find(
    (element) =>
      element && element.type === 'Identifier' && element.name === 'Boolean'
  )
  if (!booleanType) {
    return
  }
  const booleanTypeIndex = node.elements.indexOf(booleanType)
  if (booleanTypeIndex > 0) {
    context.report({
      node: booleanType,
      messageId: 'shouldBeFirst',
      suggest: [
        {
          messageId: 'moveToFirst',
          fix: (fixer) => {
            const sourceCode = context.getSourceCode()

            const elements = node.elements.slice()
            elements.splice(booleanTypeIndex, 1)
            const code = elements
              .filter(utils.isDef)
              .map((element) => sourceCode.getText(element))
            code.unshift('Boolean')

            return fixer.replaceText(node, `[${code.join(', ')}]`)
          }
        }
      ]
    })
  }
}

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

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'enforce `Boolean` comes first in component prop types',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/prefer-prop-type-boolean-first.html'
    },
    fixable: null,
    // eslint-disable-next-line eslint-plugin/require-meta-has-suggestions -- `context.report` with suggestion is not recognized in `checkArrayExpression`
    hasSuggestions: true,
    schema: [],
    messages: {
      shouldBeFirst: 'Type `Boolean` should be at first in prop types.',
      moveToFirst: 'Move `Boolean` to be first in prop types.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    /**
     * @param {import('../utils').ComponentProp} prop
     */
    function checkProperty(prop) {
      const { value } = prop
      if (!value) {
        return
      }

      if (value.type === 'ArrayExpression') {
        checkArrayExpression(value, context)
      } else if (value.type === 'ObjectExpression') {
        const type = value.properties.find(
          /** @return {property is Property} */
          (property) =>
            property.type === 'Property' &&
            utils.getStaticPropertyName(property) === 'type'
        )
        if (!type || type.value.type !== 'ArrayExpression') {
          return
        }
        checkArrayExpression(type.value, context)
      }
    }

    return utils.compositingVisitors(
      utils.defineScriptSetupVisitor(context, {
        onDefinePropsEnter(_, props) {
          props.forEach(checkProperty)
        }
      }),
      utils.executeOnVue(context, (obj) => {
        const props = utils.getComponentPropsFromOptions(obj)
        props.forEach(checkProperty)
      })
    )
  }
}
