const fs = require('fs')
const path = require('path')

const parser = require('@babel/parser')
const traverseMod = require('@babel/traverse')
const generateMod = require('@babel/generator')

const traverse = traverseMod.default || traverseMod
const generate = generateMod.default || generateMod

const { parserPluginsFor, normalizeTSTypeCastExpressionsForGenerator } = require('./utils')
const { stripTypescriptSyntaxForTemplate } = require('./strip-typescript-syntax')
const { resolveCssImports } = require('./css-resolver')
const { getConfig } = require('../config')
const { resolveBarrelOptsForLibrary } = require('../library-deps-hint')
const { inferSitePathFromFile } = require('../cms-deps-path')
const { extractDependencies } = require('./deps-extractor')
const { hoistFieldDeclarations, ensureDefaultExport } = require('./field-hoist')
const {
  tagTemplateFields,
  replaceTemplatePlaceholders,
  templatePlaceholder,
  literalFieldInitializer,
} = require('./template-field-tagger')
const { tagTemplateVariableUsages } = require('./template-var-usage-tagger')
const { extractModuleArrayConfig } = require('./array-config-extractor')
const { ensureReactDefaultImportForJsx } = require('./ensure-react-import')
const { resolveWebCodeType } = require('../transpile')

function detectCodeType(filePath) {
  const ext = path.extname(filePath).toLowerCase()
  if (ext === '.tsx' || ext === '.ts') return 'tsx'
  return 'jsx'
}

/** Prepended to v2 (`groupingStyle: 'jsdoc'`) — file marker for the visual editor / Templates. */
function prependV2TemplateFileBanner(code) {
  return `// @template v2\n\n${code}`
}

/**
 * Run the full codemod against a file's source. Does NOT touch the filesystem
 * except to read supporting files (CSS, resolved relative imports) and read/write
 * the sidecar field-map.json. The caller is responsible for writing the output
 * code to disk and for writing appended source to the cmsDependencies file.
 */
function runCodemod({
  source,
  filePath,
  cssMode = 'hybrid',
  namingStrategy = 'camelComponent',
  depsImportAlias = '@/cmsDependencies',
  depsFilePath = null,
  fieldMapPath = null,
  groupingStyle = 'lineComments',
  sitePath = null,
  componentsImportPath = null,
}) {
  const codeType = detectCodeType(filePath)
  const ast = parser.parse(source, {
    sourceType: 'module',
    plugins: parserPluginsFor(codeType),
    errorRecovery: true,
  })

  ensureDefaultExport(ast, traverse)

  const cssReport = resolveCssImports({
    ast,
    traverse,
    filePath,
    cssMode,
  })

  const config = getConfig()
  const siteForBarrel =
    sitePath ||
    config.sitePath ||
    (depsFilePath ? inferSitePathFromFile(depsFilePath) : null) ||
    (filePath ? inferSitePathFromFile(filePath) : null) ||
    null
  const barrelOpts = resolveBarrelOptsForLibrary({
    config,
    sitePath: siteForBarrel,
    componentsImportPath: componentsImportPath || undefined,
  })

  const depsReport = extractDependencies({
    ast,
    traverse,
    generate,
    filePath,
    depsImportAlias,
    depsFilePath,
    reexportLocalImportsViaBarrel: barrelOpts.reexportLocalImportsViaBarrel,
    barrelFilePath: barrelOpts.barrelFilePath,
    componentsImportPathForBarrel: barrelOpts.componentsImportPath,
  })

  const { report: arrayReport } = extractModuleArrayConfig(ast, traverse, {
    fieldMapPath,
    groupingStyle,
  })

  const { report: fieldReport, fieldRecords } = tagTemplateFields({
    ast,
    traverse,
    fieldMapPath,
    namingStrategy,
  })
  fieldReport.moduleArrayFieldCount = arrayReport.moduleArrayFieldCount || 0

  const autoFieldTypes = new Map()
  for (const r of fieldRecords || []) {
    if (!r || !r.fieldName || !r.kind) continue
    autoFieldTypes.set(r.fieldName, r.kind)
  }
  const usageTagReport = tagTemplateVariableUsages({
    ast,
    traverse,
    source,
    autoFieldTypes,
  })
  fieldReport.usageTaggedCount = usageTagReport.taggedCount

  const buildConstInitializer =
    groupingStyle === 'jsdoc' ? literalFieldInitializer : templatePlaceholder
  hoistFieldDeclarations(ast, traverse, fieldRecords, groupingStyle, buildConstInitializer)

  normalizeTSTypeCastExpressionsForGenerator(ast, traverse)

  if (codeType === 'tsx') {
    stripTypescriptSyntaxForTemplate(ast, traverse)
  }

  ensureReactDefaultImportForJsx(ast)

  const generated = generate(ast, {
    retainLines: false,
    comments: true,
    compact: false,
  }).code

  let output =
    groupingStyle === 'jsdoc' ? generated : replaceTemplatePlaceholders(generated)
  if (groupingStyle === 'jsdoc') {
    output = prependV2TemplateFileBanner(output)
  }

  const resolvedCodeType = resolveWebCodeType(output, codeType)

  return {
    output,
    codeType: resolvedCodeType,
    cssReport,
    depsReport,
    fieldReport,
  }
}

function runCodemodOnFile(filePath, options = {}) {
  const source = fs.readFileSync(filePath, 'utf8')
  return runCodemod({ ...options, source, filePath })
}

module.exports = {
  runCodemod,
  runCodemodOnFile,
  detectCodeType,
}
