const { transformSync } = require('@babel/core')
const presetEnv = require('@babel/preset-env')
const presetReact = require('@babel/preset-react')
const presetTypescript = require('@babel/preset-typescript')

const typeMap = {
  js: 'js',
  jsx: 'jsx',
  ts: 'tsx',
  tsx: 'tsx',
  html: 'html',
}

const types = Object.keys(typeMap)

class BadRequestError extends Error {
  constructor(message) {
    super(message)
    this.name = 'BadRequestError'
  }
}

/** Avoid `Foo.tsx.tsx` when `name` is already a basename with extension. */
function babelFilename(name, type) {
  const suffix = `.${typeMap[type]}`
  const base = name ?? 'name'
  return base.toLowerCase().endsWith(suffix) ? base : `${base}${suffix}`
}

/**
 * When file extension or stored codeType says jsx but the source is TypeScript (e.g. from-tsx),
 * CMS transpilers that are not TS-aware will fail.
 * @param {string} code
 * @returns {'tsx'|null}
 */
function guessWebCodeTypeFromSource(code) {
  if (!code || typeof code !== 'string') return null
  if (/\bas\s+const\b/.test(code)) return 'tsx'
  if (/\bsatisfies\b/.test(code)) return 'tsx'
  if (/import\s+type\s/.test(code)) return 'tsx'
  if (/\binterface\s+[\w$]+\s*\{/.test(code)) return 'tsx'
  if (/\btype\s+[\w$]+\s*=\s*/.test(code)) return 'tsx'
  if (/\}\s*:\s*\{/.test(code)) return 'tsx'
  return null
}

/**
 * @param {string} code
 * @param {string} initialType
 * @returns {string}
 */
function resolveWebCodeType(code, initialType) {
  if (initialType === 'html') return 'html'
  if (guessWebCodeTypeFromSource(code) === 'tsx') return 'tsx'
  return initialType
}

function transformWebCode(code, type, name) {
  process.env.BABEL_ENV = 'production'
  const filename = babelFilename(name, type)
  const sourceHasTypeScript = guessWebCodeTypeFromSource(code) === 'tsx'
  const useTypeScriptPreset =
    type === 'ts' || type === 'tsx' || sourceHasTypeScript
  const isTSX =
    type === 'tsx' ||
    (type === 'jsx' && sourceHasTypeScript) ||
    /\.tsx$/i.test(filename)
  const presets = [
    [presetEnv, { modules: 'amd' }],
    presetReact,
  ]
  if (useTypeScriptPreset) {
    presets.push([
      presetTypescript,
      { isTSX, allExtensions: true },
    ])
  }
  const options = {
    presets,
    filename,
    ast: false,
    code: true,
    configFile: false,
    babelrc: false,
    compact: true,
  }

  const result = transformSync(code, options)
  return result.code
}

/**
 * Transpile web code (JS/JSX/TS/TSX) to AMD format for browser execution.
 * HTML is returned unchanged.
 * @param {string} code - Source code
 * @param {string} type - One of js, jsx, ts, tsx, html
 * @param {string} [name] - Filename for source maps
 * @returns {string}
 */
function webTranspile(code, type, name) {
  if (!types.includes(type)) {
    throw new BadRequestError(
      `Invalid source code type. Valid types are ${types.join(', ')}`
    )
  }

  if (type === 'html') return code

  try {
    return transformWebCode(code, type, name)
  } catch (firstError) {
    if (type === 'js' || type === 'jsx') {
      try {
        return transformWebCode(code, 'tsx', name)
      } catch {
        throw firstError
      }
    }
    throw firstError
  }
}

module.exports = webTranspile
module.exports.BadRequestError = BadRequestError
module.exports.guessWebCodeTypeFromSource = guessWebCodeTypeFromSource
module.exports.resolveWebCodeType = resolveWebCodeType
