import type { Scope } from '@typescript-eslint/utils/ts-eslint'
import type { TSESTree } from '@typescript-eslint/types'
import { isIdentifier, isMemberExpression } from '../../node.ts'
import { createRule } from '../../rule.ts'

const CLASS_NAMES = new Set(['PureComponent', 'Component'])
const EXEMPT_FIELDS = new Set(['getDerivedStateFromError'])

export default createRule({
  name: 'jsx/no-class-component',
  meta: {
    type: 'problem',
    docs: {
      description: 'Disallow React Class Component',
      recommended: 'stylistic',
    },
    schema: [],
    messages: {
      invalid: 'Disallow React Class Component',
    },
  },
  defaultOptions: [],
  create(context) {
    return {
      ImportDeclaration(node) {
        if (node.source.value !== 'react') return
        const references = node.specifiers
          .flatMap((importClause) => {
            if (context.sourceCode.scopeManager) {
              return context.sourceCode.scopeManager.getDeclaredVariables(importClause)
            }
            return []
          })
          .flatMap((variable) => variable.references)
          .filter(isReactComponentReference)
        for (const reference of references) {
          context.report({
            node: reference.from.block,
            messageId: 'invalid',
          })
        }
      },
    }
  },
})

function isReactComponentReference({ isValueReference, identifier, resolved, from }: Scope.Reference): boolean {
  if (!isValueReference || from.type !== 'class') return false
  if (!isDefinitionGood(resolved)) return false
  if (isExempt(from.block.body)) return false
  if (CLASS_NAMES.has(identifier.name)) return true
  return (
    isMemberExpression(identifier.parent) &&
    isIdentifier(identifier.parent.property) &&
    CLASS_NAMES.has(identifier.parent.property.name)
  )
}

function isExempt({ body }: TSESTree.ClassBody): boolean {
  // prettier-ignore
  return body.some((element) => ("key" in element &&
    element.static &&
    element.key.type === "Identifier" &&
    EXEMPT_FIELDS.has(element.key.name)));
}

function isDefinitionGood(variable: Scope.Variable | null) {
  return variable?.defs.every(({ node }) => {
    if (node.type === 'ImportDefaultSpecifier') return true
    if (node.type === 'ImportSpecifier')
      return CLASS_NAMES.has(node.imported.type === 'Identifier' ? node.imported.name : node.imported.value)
    return false
  })
}
