import { parse, ParserPlugin } from '@babel/parser'
import MagicString from 'magic-string'

const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/
const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)as(\s*)default/s
const exportDefaultClassRE =
  /((?:^|\n|;)\s*)export\s+default\s+class\s+([\w$]+)/

/**
 * Utility for rewriting `export default` in a script block into a variable
 * declaration so that we can inject things into it
 */
export function rewriteDefault(
  input: string,
  as: string,
  parserPlugins?: ParserPlugin[]
): string {
  if (!hasDefaultExport(input)) {
    return input + `\nconst ${as} = {}`
  }

  let replaced: string | undefined

  const classMatch = input.match(exportDefaultClassRE)
  if (classMatch) {
    replaced =
      input.replace(exportDefaultClassRE, '$1class $2') +
      `\nconst ${as} = ${classMatch[2]}`
  } else {
    replaced = input.replace(defaultExportRE, `$1const ${as} =`)
  }
  if (!hasDefaultExport(replaced)) {
    return replaced
  }

  // if the script somehow still contains `default export`, it probably has
  // multi-line comments or template strings. fallback to a full parse.
  const s = new MagicString(input)
  const ast = parse(input, {
    sourceType: 'module',
    plugins: parserPlugins
  }).program.body
  ast.forEach(node => {
    if (node.type === 'ExportDefaultDeclaration') {
      s.overwrite(node.start!, node.declaration.start!, `const ${as} = `)
    }
    if (node.type === 'ExportNamedDeclaration') {
      node.specifiers.forEach(specifier => {
        if (
          specifier.type === 'ExportSpecifier' &&
          specifier.exported.type === 'Identifier' &&
          specifier.exported.name === 'default'
        ) {
          const end = specifier.end!
          s.overwrite(
            specifier.start!,
            input.charAt(end) === ',' ? end + 1 : end,
            ``
          )
          s.append(`\nconst ${as} = ${specifier.local.name}`)
        }
      })
    }
  })
  return s.toString()
}

export function hasDefaultExport(input: string): boolean {
  return defaultExportRE.test(input) || namedDefaultExportRE.test(input)
}
