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

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

const allowedPropNames = new Set(['modelValue', 'model-value'])
const allowedEventNames = new Set(['update:modelValue', 'update:model-value'])

/**
 * @param {ObjectExpression} node
 * @param {string} key
 * @returns {Literal | TemplateLiteral | undefined}
 */
function findPropertyValue(node, key) {
  const property = node.properties.find(
    (property) =>
      property.type === 'Property' &&
      property.key.type === 'Identifier' &&
      property.key.name === key
  )
  if (
    !property ||
    property.type !== 'Property' ||
    !utils.isStringLiteral(property.value)
  ) {
    return undefined
  }
  return property.value
}

/**
 * @param {RuleFixer} fixer
 * @param {Literal | TemplateLiteral} node
 * @param {string} text
 */
function replaceLiteral(fixer, node, text) {
  return fixer.replaceTextRange([node.range[0] + 1, node.range[1] - 1], text)
}

module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'disallow deprecated `model` definition (in Vue.js 3.0.0+)',
      categories: undefined,
      url: 'https://eslint.vuejs.org/rules/no-deprecated-model-definition.html'
    },
    fixable: null,
    hasSuggestions: true,
    schema: [
      {
        type: 'object',
        additionalProperties: false,
        properties: {
          allowVue3Compat: {
            type: 'boolean'
          }
        }
      }
    ],
    messages: {
      deprecatedModel: '`model` definition is deprecated.',
      vue3Compat:
        '`model` definition is deprecated. You may use the Vue 3-compatible `modelValue`/`update:modelValue` though.',
      changeToModelValue: 'Change to `modelValue`/`update:modelValue`.',
      changeToKebabModelValue: 'Change to `model-value`/`update:model-value`.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const allowVue3Compat = Boolean(context.options[0]?.allowVue3Compat)

    return utils.executeOnVue(context, (obj) => {
      const modelProperty = utils.findProperty(obj, 'model')
      if (!modelProperty || modelProperty.value.type !== 'ObjectExpression') {
        return
      }

      if (!allowVue3Compat) {
        context.report({
          node: modelProperty,
          messageId: 'deprecatedModel'
        })
        return
      }

      const propName = findPropertyValue(modelProperty.value, 'prop')
      const eventName = findPropertyValue(modelProperty.value, 'event')

      if (
        !propName ||
        !eventName ||
        !allowedPropNames.has(
          utils.getStringLiteralValue(propName, true) ?? ''
        ) ||
        !allowedEventNames.has(
          utils.getStringLiteralValue(eventName, true) ?? ''
        )
      ) {
        context.report({
          node: modelProperty,
          messageId: 'vue3Compat',
          suggest:
            propName && eventName
              ? [
                  {
                    messageId: 'changeToModelValue',
                    *fix(fixer) {
                      const newPropName = 'modelValue'
                      const newEventName = 'update:modelValue'
                      yield replaceLiteral(fixer, propName, newPropName)
                      yield replaceLiteral(fixer, eventName, newEventName)
                    }
                  },
                  {
                    messageId: 'changeToKebabModelValue',
                    *fix(fixer) {
                      const newPropName = 'model-value'
                      const newEventName = 'update:model-value'
                      yield replaceLiteral(fixer, propName, newPropName)
                      yield replaceLiteral(fixer, eventName, newEventName)
                    }
                  }
                ]
              : []
        })
      }
    })
  }
}
