import { DEPENDENCIES_FIELDS } from '@pnpm/types'
import loadYamlFile = require('load-yaml-file')
import path = require('path')
import {
  CURRENT_SHRINKWRAP_FILENAME,
  SHRINKWRAP_VERSION,
  WANTED_SHRINKWRAP_FILENAME,
} from './constants'
import { ShrinkwrapBreakingChangeError } from './errors'
import logger from './logger'
import { Shrinkwrap } from './types'

export const readPrivate = readCurrent

export async function readCurrent (
  pkgPath: string,
  opts: {
    wantedVersion?: number,
    ignoreIncompatible: boolean,
  },
): Promise<Shrinkwrap | null> {
  const shrinkwrapPath = path.join(pkgPath, CURRENT_SHRINKWRAP_FILENAME)
  return _read(shrinkwrapPath, pkgPath, opts)
}

export const read = readWanted

export async function readWanted (
  pkgPath: string,
  opts: {
    wantedVersion?: number,
    ignoreIncompatible: boolean,
  },
): Promise<Shrinkwrap | null> {
  const shrinkwrapPath = path.join(pkgPath, WANTED_SHRINKWRAP_FILENAME)
  return _read(shrinkwrapPath, pkgPath, opts)
}

async function _read (
  shrinkwrapPath: string,
  prefix: string,
  opts: {
    wantedVersion?: number,
    ignoreIncompatible: boolean,
  },
): Promise<Shrinkwrap | null> {
  let shrinkwrap
  try {
    shrinkwrap = await loadYamlFile<Shrinkwrap>(shrinkwrapPath)
  } catch (err) {
    if ((err as NodeJS.ErrnoException).code !== 'ENOENT') {
      throw err
    }
    return null
  }
  // tslint:disable:no-string-literal
  if (shrinkwrap && typeof shrinkwrap['specifiers'] !== 'undefined') {
    shrinkwrap.importers = {
      '.': {
        specifiers: shrinkwrap['specifiers'],
      },
    }
    delete shrinkwrap['specifiers']
    for (const depType of DEPENDENCIES_FIELDS) {
      if (shrinkwrap[depType]) {
        shrinkwrap.importers['.'][depType] = shrinkwrap[depType]
        delete shrinkwrap[depType]
      }
    }
  }
  if (shrinkwrap) {
    // for backward compatibility
    if (shrinkwrap['version'] && !shrinkwrap.shrinkwrapVersion) {
      shrinkwrap.shrinkwrapVersion = shrinkwrap['version']
      delete shrinkwrap['version']
    }
    if (typeof shrinkwrap['shrinkwrapMinorVersion'] === 'number') {
      shrinkwrap.shrinkwrapVersion = +`${shrinkwrap.shrinkwrapVersion}.${shrinkwrap['shrinkwrapMinorVersion']}`
    }
    // tslint:enable:no-string-literal
    if (typeof opts.wantedVersion !== 'number' || Math.floor(shrinkwrap.shrinkwrapVersion) === Math.floor(opts.wantedVersion)) {
      if (typeof opts.wantedVersion === 'number' && shrinkwrap.shrinkwrapVersion > opts.wantedVersion) {
        logger.warn({
          message: 'Your shrinkwrap.yaml was generated by a newer version of pnpm. ' +
            `It is a compatible version but it might get downgraded to version ${opts.wantedVersion}`,
          prefix,
        })
      }
      return shrinkwrap
    }
  }
  if (opts.ignoreIncompatible) {
    logger.warn({
      message: `Ignoring not compatible shrinkwrap file at ${shrinkwrapPath}`,
      prefix,
    })
    return null
  }
  throw new ShrinkwrapBreakingChangeError(shrinkwrapPath)
}

export function create (
  registry: string,
  importerIds: string[],
  opts: {
    shrinkwrapVersion: number,
  },
) {
  const importers = importerIds.reduce((acc, importerId) => {
    acc[importerId] = {
      dependencies: {},
      specifiers: {},
    }
    return acc
  }, {})
  return {
    importers,
    registry,
    shrinkwrapVersion: opts.shrinkwrapVersion || SHRINKWRAP_VERSION,
  }
}
