// add and remove dependency specs to/from pkg manifest

const removeFromOthers = (name, type, pkg) => {
  const others = new Set([
    'dependencies',
    'optionalDependencies',
    'devDependencies',
    'peerDependenciesMeta',
    'peerDependencies',
  ])

  switch (type) {
    case 'prod':
      others.delete('dependencies')
      break
    case 'dev':
      others.delete('devDependencies')
      break
    case 'optional':
      others.delete('optionalDependencies')
      break
    case 'peer':
    case 'peerOptional':
      others.delete('devDependencies')
      others.delete('peerDependencies')
      others.delete('peerDependenciesMeta')
      break
  }

  for (const other of others)
    deleteSubKey(pkg, other, name)
}

const add = ({pkg, add, saveBundle, saveType}) => {
  for (const spec of add)
    addSingle({pkg, spec, saveBundle, saveType})

  return pkg
}

const addSingle = ({pkg, spec, saveBundle, saveType}) => {
  if (!saveType)
    saveType = getSaveType(pkg, spec)

  const { name, rawSpec } = spec
  removeFromOthers(name, saveType, pkg)
  const type = saveType === 'prod' ? 'dependencies'
    : saveType === 'optional' ? 'optionalDependencies'
    : saveType === 'peer' || saveType === 'peerOptional' ? 'peerDependencies'
    : saveType === 'dev' ? 'devDependencies'
    : /* istanbul ignore next */ null

  pkg[type] = pkg[type] || {}
  if (rawSpec !== '' || pkg[type][name] === undefined)
    pkg[type][name] = rawSpec || '*'

  if (saveType === 'peer' || saveType === 'peerOptional') {
    const pdm = pkg.peerDependenciesMeta || {}
    if (saveType === 'peer' && pdm[name] && pdm[name].optional)
      pdm[name].optional = false
    else if (saveType === 'peerOptional') {
      pdm[name] = pdm[name] || {}
      pdm[name].optional = true
      pkg.peerDependenciesMeta = pdm
    }
    // peerDeps are often also a devDep, so that they can be tested when
    // using package managers that don't auto-install peer deps
    if (pkg.devDependencies && pkg.devDependencies[name] !== undefined)
      pkg.devDependencies[name] = pkg.peerDependencies[name]
  }

  if (saveBundle) {
    // keep it sorted, keep it unique
    const bd = new Set(pkg.bundleDependencies || [])
    bd.add(spec.name)
    pkg.bundleDependencies = [...bd].sort((a, b) => a.localeCompare(b))
  }
}

const getSaveType = (pkg, spec) => {
  const {name} = spec
  const {
    // these names are so lonnnnngggg
    devDependencies: devDeps,
    optionalDependencies: optDeps,
    peerDependencies: peerDeps,
    peerDependenciesMeta: peerDepsMeta,
  } = pkg

  if (peerDeps && peerDeps[name] !== undefined) {
    if (peerDepsMeta && peerDepsMeta[name] && peerDepsMeta[name].optional)
      return 'peerOptional'
    else
      return 'peer'
  } else if (devDeps && devDeps[name] !== undefined)
    return 'dev'
  else if (optDeps && optDeps[name] !== undefined)
    return 'optional'
  else
    return 'prod'
}

const deleteSubKey = (obj, k, sk) => {
  if (obj[k]) {
    delete obj[k][sk]
    if (!Object.keys(obj[k]).length)
      delete obj[k]
  }
}

const rm = (pkg, rm) => {
  for (const type of [
    'dependencies',
    'optionalDependencies',
    'peerDependencies',
    'peerDependenciesMeta',
    'devDependencies',
  ]) {
    for (const name of rm)
      deleteSubKey(pkg, type, name)
  }
  if (pkg.bundleDependencies) {
    pkg.bundleDependencies = pkg.bundleDependencies
      .filter(name => !rm.includes(name))
    if (!pkg.bundleDependencies.length)
      delete pkg.bundleDependencies
  }
  return pkg
}

module.exports = { add, rm }
