import { execFileSync } from 'node:child_process'
import { readFileSync, writeFileSync } from 'node:fs'

import { findLatestVersionInReleaseLine, resolveReleasePlan } from './release-version.mjs'

function parseArgs(argv) {
  const args = {}

  for (let index = 0; index < argv.length; index += 1) {
    const key = argv[index]

    if (!key.startsWith('--')) {
      continue
    }

    args[key.slice(2)] = argv[index + 1]
    index += 1
  }

  return args
}

function exec(command, args, options = {}) {
  return execFileSync(command, args, {
    encoding: 'utf8',
    stdio: ['inherit', 'pipe', 'inherit'],
    ...options,
  }).trim()
}

function run(command, args) {
  execFileSync(command, args, { stdio: 'inherit' })
}

function formatNpmVersion(version) {
  return version.startsWith('v') ? version : `v${version}`
}

function readPackageJson() {
  return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'))
}

function writePackageJson(pkg) {
  const packageJsonUrl = new URL('../package.json', import.meta.url)
  writeFileSync(packageJsonUrl, `${JSON.stringify(pkg, null, 2)}\n`)
}

function getPublishedVersions(packageName) {
  try {
    const output = exec('npm', ['view', packageName, 'versions', '--json'])
    const parsed = JSON.parse(output)

    return Array.isArray(parsed) ? parsed : [parsed]
  }
  catch {
    return []
  }
}

function setPackageJson(pkg, updates) {
  let dirty = false

  for (const [key, value] of Object.entries(updates)) {
    if (value != null && pkg[key] !== value) {
      pkg[key] = value
      dirty = true
    }
  }

  if (dirty) {
    writePackageJson(pkg)
  }
}

function main() {
  const args = parseArgs(process.argv.slice(2))
  const releaseType = args.type
  const packageName = args.name
  const tagPrefix = args['tag-prefix'] ?? null

  if (!releaseType || !packageName) {
    throw new Error('Usage: node scripts/ci-release.mjs --type <patch|prerelease> --name <package-name> [--tag-prefix <prefix>]')
  }

  if (!['patch', 'prerelease'].includes(releaseType)) {
    throw new Error(`Unsupported release type: ${releaseType}`)
  }

  const pkg = readPackageJson()
  const publishedVersion = findLatestVersionInReleaseLine(pkg.version, getPublishedVersions(packageName))
  const plan = resolveReleasePlan(pkg.version, publishedVersion)

  setPackageJson(pkg, {
    name: packageName,
    version: plan.syncedPackageVersion,
  })

  let nextVersion

  if (plan.releaseVersion) {
    nextVersion = pkg.version === plan.releaseVersion
      ? formatNpmVersion(plan.releaseVersion)
      : exec('npm', ['version', '--no-git-tag-version', plan.releaseVersion])
  }
  else if (releaseType === 'patch') {
    nextVersion = exec('npm', ['version', '--no-git-tag-version', 'patch'])
  }
  else {
    nextVersion = exec('npm', ['version', '--no-git-tag-version', 'prerelease'])
  }

  if (releaseType === 'prerelease' && plan.releaseVersion) {
    nextVersion = exec('npm', ['version', '--no-git-tag-version', 'prerelease'])
  }

  run('npm', ['publish'])

  if (releaseType === 'patch') {
    const gitTag = `${tagPrefix ?? ''}${nextVersion}`
    run('git', ['tag', gitTag])
    run('git', ['push', `https://gitlab-ci-token:${process.env.CI_JOB_TOKEN}@${process.env.CI_SERVER_HOST}/${process.env.CI_PROJECT_PATH}.git`, gitTag])
  }
}

main()
