import { promises } from 'fs'
import '../server/node-polyfill-fetch'
import * as Log from '../build/output/log'
import findUp from 'next/dist/compiled/find-up'

/**
 * Attempts to patch npm package-lock.json when it
 * fails to include optionalDependencies for other platforms
 * this can occur when the package-lock is rebuilt from a current
 * node_modules install instead of pulling fresh package data
 */
export async function patchIncorrectLockfile(dir: string) {
  const lockfilePath = await findUp('package-lock.json', { cwd: dir })

  if (!lockfilePath) {
    // if no lockfile present there is no action to take
    return
  }
  const content = await promises.readFile(lockfilePath, 'utf8')
  const lockfileParsed = JSON.parse(content)

  const foundSwcPkgs = new Set()
  const nextPkg = lockfileParsed.packages?.['node_modules/next']

  // if we don't find next in the package-lock we can't continue
  if (!nextPkg) {
    return
  }
  const nextVersion = nextPkg.version
  const packageKeys = Object.keys(lockfileParsed.dependencies || {})

  const expectedSwcPkgs = Object.keys(nextPkg?.optionalDependencies).filter(
    (pkg) => pkg.startsWith('@next/swc-')
  )

  packageKeys.forEach((pkgKey) => {
    const swcIndex = pkgKey.indexOf('@next/swc-')
    if (swcIndex > -1) {
      foundSwcPkgs.add(pkgKey.substring(swcIndex))
    }
  })

  // if swc package keys are missing manually populate them
  // so installs on different platforms can succeed
  // user will need to run npm i after to ensure it's corrected
  if (foundSwcPkgs.size !== expectedSwcPkgs.length) {
    Log.warn(`Found lockfile missing swc dependencies, patching..`)

    try {
      // populate fields for each missing swc pkg
      for (const pkg of expectedSwcPkgs) {
        if (!foundSwcPkgs.has(pkg)) {
          const res = await fetch(`https://registry.npmjs.org/${pkg}`)

          if (!res.ok) {
            throw new Error(
              `Failed to fetch registry info for ${pkg}, got status ${res.status}`
            )
          }
          const data = await res.json()
          const version = data.versions[nextVersion]

          if (!version) {
            throw new Error(
              `Failed to find matching version for ${pkg} at ${nextVersion}`
            )
          }
          if (lockfileParsed.dependencies) {
            lockfileParsed.dependencies[pkg] = {
              version: nextVersion,
              resolved: version.dist.tarball,
              integrity: version.dist.integrity,
              optional: true,
            }
          }
          lockfileParsed.packages[`node_modules/${pkg}`] = {
            version: nextVersion,
            resolved: version.dist.tarball,
            integrity: version.dist.integrity,
            cpu: version.cpu,
            optional: true,
            os: version.os,
            engines: version.engines,
          }
        }
      }

      await promises.writeFile(
        lockfilePath,
        JSON.stringify(lockfileParsed, null, 2)
      )
      Log.warn(
        'Lockfile was successfully patched, please run "npm install" to ensure @next/swc dependencies are downloaded'
      )
    } catch (err) {
      Log.error(
        `Failed to patch lockfile, please try uninstalling and reinstalling next in this workspace`
      )
      console.error(err)
    }
  }
}
