"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = validateVersionInstance;

function _ramda() {
  const data = _interopRequireDefault(require("ramda"));

  _ramda = function () {
    return data;
  };

  return data;
}

function _packageJsonValidator() {
  const data = require("package-json-validator");

  _packageJsonValidator = function () {
    return data;
  };

  return data;
}

function _validateNpmPackageName() {
  const data = _interopRequireDefault(require("validate-npm-package-name"));

  _validateNpmPackageName = function () {
    return data;
  };

  return data;
}

function _validateType() {
  const data = _interopRequireDefault(require("../utils/validate-type"));

  _validateType = function () {
    return data;
  };

  return data;
}

function _bitId() {
  const data = require("../bit-id");

  _bitId = function () {
    return data;
  };

  return data;
}

function _versionInvalid() {
  const data = _interopRequireDefault(require("./exceptions/version-invalid"));

  _versionInvalid = function () {
    return data;
  };

  return data;
}

function _utils() {
  const data = require("../utils");

  _utils = function () {
    return data;
  };

  return data;
}

function _dependencies() {
  const data = require("../consumer/component/dependencies");

  _dependencies = function () {
    return data;
  };

  return data;
}

function _packageJsonFile() {
  const data = _interopRequireDefault(require("../consumer/component/package-json-file"));

  _packageJsonFile = function () {
    return data;
  };

  return data;
}

function _consumerOverrides() {
  const data = require("../consumer/config/consumer-overrides");

  _consumerOverrides = function () {
    return data;
  };

  return data;
}

function _componentOverrides() {
  const data = require("../consumer/config/component-overrides");

  _componentOverrides = function () {
    return data;
  };

  return data;
}

function _dependencies2() {
  const data = require("../consumer/component/dependencies/dependencies");

  _dependencies2 = function () {
    return data;
  };

  return data;
}

/**
 * make sure a Version instance is correct. throw an exceptions if it is not.
 */
function validateVersionInstance(version) {
  const message = 'unable to save Version object';

  const validateBitIdStr = (bitIdStr, field, validateVersion = true) => {
    (0, _validateType().default)(message, bitIdStr, field, 'string');
    let bitId;

    try {
      bitId = _bitId().BitId.parse(bitIdStr, true);
    } catch (err) {
      throw new (_versionInvalid().default)(`${message}, the ${field} has an invalid Bit id`);
    }

    if (validateVersion && !bitId.hasVersion()) {
      throw new (_versionInvalid().default)(`${message}, the ${field} ${bitIdStr} does not have a version`);
    }

    if (!bitId.scope) throw new (_versionInvalid().default)(`${message}, the ${field} ${bitIdStr} does not have a scope`);
  };

  const _validateEnv = env => {
    if (!env) return;

    if (typeof env === 'string') {
      // Do not validate version - for backward compatibility
      validateBitIdStr(env, 'environment-id', false);
      return;
    }

    (0, _validateType().default)(message, env, 'env', 'object');

    if (!env.name) {
      throw new (_versionInvalid().default)(`${message}, the environment is missing the name attribute`);
    }

    validateBitIdStr(env.name, 'env.name');

    if (env.files) {
      const compilerName = env.name || '';
      env.files.forEach(file => {
        if (!file.name) {
          throw new (_versionInvalid().default)(`${message}, the environment ${compilerName} has a file which missing the name attribute`);
        }
      });
    }
  };
  /**
   * Validate that the package name and version are valid
   * @param {*} packageName
   * @param {*} packageVersion
   */


  const _validatePackageDependency = (packageVersion, packageName) => {
    const packageNameValidateResult = (0, _validateNpmPackageName().default)(packageName);

    if (!packageNameValidateResult.validForNewPackages && !packageNameValidateResult.validForOldPackages) {
      const errors = packageNameValidateResult.errors || [];
      throw new (_versionInvalid().default)(`${packageName} is invalid package name, errors:  ${errors.join()}`);
    } // don't use semver.valid and semver.validRange to validate the package version because it
    // can be also a URL, Git URL or Github URL. see here: https://docs.npmjs.com/files/package.json#dependencies


    (0, _validateType().default)(message, packageVersion, `version of "${packageName}"`, 'string');
  };

  const _validatePackageDependencies = packageDependencies => {
    (0, _validateType().default)(message, packageDependencies, 'packageDependencies', 'object');

    _ramda().default.forEachObjIndexed(_validatePackageDependency, packageDependencies);
  };

  const validateFile = (file, isDist = false) => {
    const field = isDist ? 'dist-file' : 'file';
    (0, _validateType().default)(message, file, field, 'object');

    if (!(0, _utils().isValidPath)(file.relativePath)) {
      throw new (_versionInvalid().default)(`${message}, the ${field} ${file.relativePath} is invalid`);
    }

    if (!file.name) {
      throw new (_versionInvalid().default)(`${message}, the ${field} ${file.relativePath} is missing the name attribute`);
    }

    if (!file.file) throw new (_versionInvalid().default)(`${message}, the ${field} ${file.relativePath} is missing the hash`);
    (0, _validateType().default)(message, file.name, `${field}.name`, 'string');
    (0, _validateType().default)(message, file.file, `${field}.file`, 'object');
    (0, _validateType().default)(message, file.file.hash, `${field}.file.hash`, 'string');
  };

  if (!version.mainFile) throw new (_versionInvalid().default)(`${message}, the mainFile is missing`);

  if (!(0, _utils().isValidPath)(version.mainFile)) {
    throw new (_versionInvalid().default)(`${message}, the mainFile ${version.mainFile} is invalid`);
  }

  if (!version.files || !version.files.length) throw new (_versionInvalid().default)(`${message}, the files are missing`);
  let foundMainFile = false;
  (0, _validateType().default)(message, version.files, 'files', 'array');
  const filesPaths = [];
  version.files.forEach(file => {
    validateFile(file);
    filesPaths.push(file.relativePath);
    if (file.relativePath === version.mainFile) foundMainFile = true;
  });

  if (!foundMainFile) {
    throw new (_versionInvalid().default)(`${message}, unable to find the mainFile ${version.mainFile} in the files list`);
  }

  const duplicateFiles = filesPaths.filter(file => filesPaths.filter(f => file.toLowerCase() === f.toLowerCase()).length > 1);

  if (duplicateFiles.length) {
    throw new (_versionInvalid().default)(`${message} the following files are duplicated ${duplicateFiles.join(', ')}`);
  }

  _validateEnv(version.compiler);

  _validateEnv(version.tester);

  _validatePackageDependencies(version.packageDependencies);

  _validatePackageDependencies(version.devPackageDependencies);

  _validatePackageDependencies(version.peerPackageDependencies);

  _validatePackageDependencies(version.compilerPackageDependencies);

  _validatePackageDependencies(version.testerPackageDependencies);

  if (version.dists && version.dists.length) {
    (0, _validateType().default)(message, version.dists, 'dist', 'array'); // $FlowFixMe

    version.dists.forEach(file => {
      validateFile(file, true);
    });
  } else if (version.mainDistFile) {
    throw new (_versionInvalid().default)(`${message} the mainDistFile cannot be set when the dists are empty`);
  }

  if (version.mainDistFile && !(0, _utils().isValidPath)(version.mainDistFile)) {
    throw new (_versionInvalid().default)(`${message}, the mainDistFile ${version.mainDistFile} is invalid`);
  }

  _dependencies2().DEPENDENCIES_TYPES.forEach(dependenciesType => {
    // $FlowFixMe
    if (!(version[dependenciesType] instanceof _dependencies().Dependencies)) {
      throw new (_versionInvalid().default)(`${message}, ${dependenciesType} must be an instance of Dependencies, got ${typeof version[dependenciesType]}`);
    }
  });

  version.dependencies.validate();
  version.devDependencies.validate();
  version.compilerDependencies.validate();
  version.testerDependencies.validate();

  if (!version.dependencies.isEmpty() && !version.flattenedDependencies.length) {
    throw new (_versionInvalid().default)(`${message}, it has dependencies but its flattenedDependencies is empty`);
  }

  if (!version.devDependencies.isEmpty() && !version.flattenedDevDependencies.length) {
    throw new (_versionInvalid().default)(`${message}, it has devDependencies but its flattenedDevDependencies is empty`);
  }

  if (!version.compilerDependencies.isEmpty() && !version.flattenedCompilerDependencies.length) {
    throw new (_versionInvalid().default)(`${message}, it has compilerDependencies but its flattenedCompilerDependencies is empty`);
  }

  if (!version.testerDependencies.isEmpty() && !version.flattenedTesterDependencies.length) {
    throw new (_versionInvalid().default)(`${message}, it has testerDependencies but its flattenedTesterDependencies is empty`);
  }

  const validateFlattenedDependencies = dependencies => {
    (0, _validateType().default)(message, dependencies, 'dependencies', 'array');
    dependencies.forEach(dependency => {
      if (!(dependency instanceof _bitId().BitId)) {
        throw new (_versionInvalid().default)(`${message}, a flattenedDependency expected to be BitId, got ${typeof dependency}`);
      }

      if (!dependency.hasVersion()) {
        throw new (_versionInvalid().default)(`${message}, the flattenedDependency ${dependency.toString()} does not have a version`);
      }
    });
  };

  validateFlattenedDependencies(version.flattenedDependencies);
  validateFlattenedDependencies(version.flattenedDevDependencies);
  validateFlattenedDependencies(version.flattenedCompilerDependencies);
  validateFlattenedDependencies(version.flattenedTesterDependencies);
  if (!version.log) throw new (_versionInvalid().default)(`${message}, the log object is missing`);
  (0, _validateType().default)(message, version.log, 'log', 'object');

  if (version.bindingPrefix) {
    (0, _validateType().default)(message, version.bindingPrefix, 'bindingPrefix', 'string');
  }

  const npmSpecs = _packageJsonValidator().PJV.getSpecMap('npm');

  const validatePackageJsonField = (fieldName, fieldValue) => {
    if (!npmSpecs[fieldName]) {
      // it's not a standard package.json field, can't validate
      return null;
    }

    const validateResult = _packageJsonValidator().PJV.validateType(fieldName, npmSpecs[fieldName], fieldValue);

    if (!validateResult.length) return null;
    return validateResult.join(', ');
  };

  const validateOverrides = (fieldValue, fieldName) => {
    const field = `overrides.${fieldName}`;

    if (_consumerOverrides().dependenciesFields.includes(fieldName)) {
      (0, _validateType().default)(message, fieldValue, field, 'object');
      Object.keys(fieldValue).forEach(key => {
        (0, _validateType().default)(message, key, `property name of ${field}`, 'string');
        (0, _validateType().default)(message, fieldValue[key], `version of "${field}.${key}"`, 'string');
      });
    } else if (!_consumerOverrides().nonPackageJsonFields.includes(fieldName)) {
      const result = validatePackageJsonField(fieldName, fieldValue);

      if (result) {
        throw new (_versionInvalid().default)(`${message}, "${field}" is a package.json field but is not compliant with npm requirements. ${result}`);
      }
    }
  };

  Object.keys(version.overrides).forEach(field => {
    if (_componentOverrides().componentOverridesForbiddenFields.includes(field)) {
      throw new (_versionInvalid().default)(`${message}, the "overrides" has a forbidden key "${field}"`);
    } // $FlowFixMe


    validateOverrides(version.overrides[field], field);
  });
  (0, _validateType().default)(message, version.packageJsonChangedProps, 'packageJsonChangedProps', 'object');

  const forbiddenPackageJsonProps = _packageJsonFile().default.propsNonUserChangeable();

  Object.keys(version.packageJsonChangedProps).forEach(prop => {
    (0, _validateType().default)(message, prop, 'property name of packageJson', 'string');

    if (forbiddenPackageJsonProps.includes(prop)) {
      throw new (_versionInvalid().default)(`${message}, the packageJsonChangedProps should not override the prop ${prop}`);
    }

    const result = validatePackageJsonField(prop, version.packageJsonChangedProps[prop]);

    if (result) {
      throw new (_versionInvalid().default)(`${message}, the generated package.json field "${prop}" is not compliant with npm requirements. ${result}`);
    }
  });
}