const semver = require('semver')
const log = require('proc-log')
const pacote = require('pacote')
const { run, git, npm, pkg: cli, spawn } = require('./util.js')

const resetdeps = () => npm('run', 'resetdeps')

const op = () => spawn('op', 'item', 'get', 'npm', '--otp', { out: true, ok: true })

const getVersion = async (s) => {
  const mani = await pacote.manifest(s, { preferOnline: true }).catch(() => null)
  return mani?.version
}
const getLatestMajor = async (s) => {
  const pack = await pacote.packument(s, { preferOnline: true }).catch(() => null)
  return pack?.['dist-tags']?.latest ? semver.major(pack['dist-tags'].latest) : 0
}

const TAG = {
  cli: ({ version }) => `next-${semver.major(version)}`,
  workspace: async ({ name, version }) => {
    const { prerelease, major } = semver.parse(version)
    if (prerelease.length) {
      return 'prerelease'
    }
    if (major >= await getLatestMajor(name)) {
      return 'latest'
    }
    return 'backport'
  },
}

const needsPublish = async ({ private, name, version }, { force, getTag }) => {
  if (private) {
    return
  }

  const tag = await getTag({ name, version })
  if (force || version !== await getVersion(`${name}@${tag}`)) {
    return tag
  }
}

const getPublishes = async (opts) => {
  const publish = []

  for (const { name, pkg } of await cli.mapWorkspaces()) {
    publish.push({
      workspace: name,
      tag: await needsPublish(pkg, { ...opts, getTag: TAG.workspace }),
    })
  }

  publish.push({
    tag: await needsPublish(cli, { ...opts, getTag: TAG.cli }),
  })

  return publish.filter(p => p.tag)
}

const main = async (opts) => {
  const packOnly = opts.pack || opts.packDestination
  const publishes = await getPublishes({ force: packOnly })

  if (!publishes.length) {
    throw new Error(
      'Nothing to publish, exiting. ' +
      'All packages to publish should have their version bumped before running this script.'
    )
  }

  log.info('publish', '\n' + publishes.map(JSON.stringify).join('\n'))

  await git('clean', '-fd')
  await resetdeps()
  await npm('ls', '--omit=dev', { quiet: true })
  await npm('rm', '--global', '--force', 'npm')
  await npm('link', '--force', '--ignore-scripts')

  if (opts.test) {
    await npm('run', 'lint-all', '--ignore-scripts')
    await npm('run', 'postlint', '--ignore-scripts')
    await npm('run', 'test-all', '--ignore-scripts')
  }

  await npm('prune', '--omit=dev', '--no-save', '--no-audit', '--no-fund')
  await npm('install', '-w', 'docs', '--ignore-scripts', '--no-audit', '--no-fund')
  await git.dirty()

  for (const p of publishes) {
    const workspace = p.workspace && `--workspace=${p.workspace}`
    if (packOnly) {
      await npm(
        'pack',
        workspace,
        opts.packDestination && `--pack-destination=${opts.packDestination}`
      )
    } else {
      await npm(
        'publish',
        workspace,
        `--tag=${p.tag}`,
        opts.dryRun && '--dry-run',
        opts.otp && `--otp=${opts.otp === 'op' ? await op() : opts.otp}`
      )
    }
  }
}

run(main).catch(resetdeps)
