/**
 * Build npm package to be able to embed them in the binary
 */

import { mkdir, writeFile, rm, readFile } from 'node:fs/promises'
import { existsSync } from 'node:fs'
import { createRequire } from 'node:module'
import { join, dirname, basename, extname } from 'node:path'
import { fileURLToPath } from 'node:url'

import ncc from '@vercel/ncc'
import { findUp } from 'find-up'

const __dirname = dirname(fileURLToPath(import.meta.url))
const require = createRequire(import.meta.url)

/**
 * @type {{
 *   name: string;
 *   type: "cjs" | "module" | "module-default";
 *   types?: string;
 * }[]}
 */
const packages = [
  {
    name: 'anser',
    type: 'cjs',
  },
  {
    name: 'css.escape',
    type: 'cjs',
    types: 'export = CSS.escape',
  },
  {
    name: 'platform',
    type: 'cjs',
  },
  {
    name: 'source-map',
    type: 'module',
  },
  {
    name: 'stacktrace-parser',
    type: 'module',
  },
  {
    name: 'strip-ansi',
    type: 'module-default',
  },
]
const externals = Object.fromEntries(
  packages.map((pkg) => [
    pkg.name,
    `@vercel/turbopack-next/compiled/${pkg.name}`,
  ])
)

// adapted from https://github.com/vercel/next.js/blob/8fb5ef18e7958a19874e11b8037ac0f71c48baef/packages/next/taskfile-ncc.js
async function writePackageManifest(packageName, main) {
  // some newer packages fail to include package.json in the exports
  // so we can't reliably use require.resolve here
  let packagePath

  try {
    packagePath = require.resolve(packageName + '/package.json')
  } catch (_) {
    packagePath = await findUp('package.json', {
      cwd: dirname(require.resolve(packageName)),
    })
  }
  const { name, author, license } = require(packagePath)

  const compiledPackagePath = join(__dirname, `src/compiled/${packageName}`)

  const potentialLicensePath = join(dirname(packagePath), './LICENSE')
  if (existsSync(potentialLicensePath)) {
    await writeFile(
      join(compiledPackagePath, 'LICENSE'),
      await readFile(potentialLicensePath, 'utf8')
    )
  } else {
    // license might be lower case and not able to be found on case-sensitive
    // file systems (ubuntu)
    const otherPotentialLicensePath = join(dirname(packagePath), './license')
    if (existsSync(otherPotentialLicensePath)) {
      await writeFile(
        join(compiledPackagePath, 'LICENSE'),
        await readFile(otherPotentialLicensePath, 'utf8')
      )
    }
  }

  await writeFile(
    join(compiledPackagePath, 'package.json'),
    JSON.stringify(
      Object.assign(
        {},
        { name, main: `${basename(main, '.' + extname(main))}` },
        author ? { author } : undefined,
        license ? { license } : undefined
      )
    ) + '\n'
  )
}

async function main() {
  const baseDir = join(__dirname, 'src/compiled')

  await rm(baseDir, {
    force: true,
    recursive: true,
  })

  let types = '/* GENERATED FILE, DO NOT EDIT */\n'

  for (const pkg of packages) {
    const input = require.resolve(pkg.name)

    const outputDir = join(baseDir, pkg.name)
    await mkdir(outputDir, { recursive: true })

    const { code, assets } = await ncc(input, {
      minify: true,
      assetBuilds: true,
      quiet: true,
      externals,
    })

    await writeFile(join(outputDir, 'index.js'), code)

    for (const key in assets) {
      await writeFile(join(outputDir, key), assets[key].source)
    }

    await writePackageManifest(pkg.name, 'index.js')

    types += `\ndeclare module '@vercel/turbopack-next/compiled/${pkg.name}' {\n`
    if (pkg.types) {
      types += `  ${pkg.types}\n`
    } else if (pkg.type === 'module-default') {
      types += `  import m from '${pkg.name}'\n`
      types += `  export default m\n`
    } else if (pkg.type === 'module') {
      types += `  export * from '${pkg.name}'\n`
    } else if (pkg.type === 'cjs') {
      types += `  import m from '${pkg.name}'\n`
      types += `  export = m\n`
    } else {
      throw new Error(`unknown package type ${pkg.type} for ${pkg.name}`)
    }
    types += `}\n`

    console.log(`built ${pkg.name}`)
  }

  await writeFile(join(__dirname, 'types', 'compiled.d.ts'), types)
}

main().catch((e) => {
  console.dir(e)
  process.exit(1)
})
