const t = require('@babel/types')

function stripParamType(node) {
  if (!node) return
  if (node.typeAnnotation) node.typeAnnotation = null
  if (t.isIdentifier(node) && node.optional) node.optional = false
  if (t.isAssignmentPattern(node)) stripParamType(node.left)
  if (t.isRestElement(node)) stripParamType(node.argument)
  if (t.isObjectPattern(node)) {
    for (const prop of node.properties) {
      if (t.isObjectProperty(prop)) stripParamType(prop.value)
      if (t.isRestElement(prop)) stripParamType(prop.argument)
    }
  }
  if (t.isArrayPattern(node)) {
    for (const el of node.elements) {
      if (el) stripParamType(el)
    }
  }
}

function stripFunctionLikeNode(fnNode) {
  if (!fnNode) return
  fnNode.returnType = null
  fnNode.typeParameters = null
  fnNode.predicate = null
  for (const param of fnNode.params) {
    stripParamType(param)
  }
}

/**
 * Removes TypeScript-only syntax so template output is valid for JSX parsers (e.g. CMS
 * template libraries using .jsx + a non-TS pipeline). Runs on the codemod AST before print.
 */
function stripTypescriptSyntaxForTemplate(ast, traverseFn) {
  traverseFn(ast, {
    ImportDeclaration(path) {
      if (path.node.importKind === 'type') path.remove()
    },
    ExportNamedDeclaration(path) {
      if (path.node.exportKind === 'type') {
        path.remove()
        return
      }
      const specs = path.node.specifiers
      if (specs && specs.length) {
        path.node.specifiers = specs.filter((s) => s.exportKind !== 'type')
        if (path.node.specifiers.length === 0 && !path.node.declaration) path.remove()
      }
    },
    TSAsExpression(path) {
      path.replaceWith(path.node.expression)
    },
    TSSatisfiesExpression(path) {
      path.replaceWith(path.node.expression)
    },
    TSNonNullExpression(path) {
      path.replaceWith(path.node.expression)
    },
    FunctionDeclaration(path) {
      stripFunctionLikeNode(path.node)
    },
    FunctionExpression(path) {
      stripFunctionLikeNode(path.node)
    },
    ArrowFunctionExpression(path) {
      stripFunctionLikeNode(path.node)
    },
    ClassMethod(path) {
      stripFunctionLikeNode(path.node)
    },
    ClassPrivateMethod(path) {
      stripFunctionLikeNode(path.node)
    },
    ObjectMethod(path) {
      stripFunctionLikeNode(path.node)
    },
    ClassDeclaration(path) {
      path.node.typeParameters = null
      path.node.implements = null
      path.node.superTypeParameters = null
    },
    VariableDeclarator(path) {
      const id = path.node.id
      if (id && t.isIdentifier(id) && id.typeAnnotation) id.typeAnnotation = null
    },
  })
}

module.exports = {
  stripTypescriptSyntaxForTemplate,
}
