"use strict";

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

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

function _bluebird() {
  const data = require("bluebird");

  _bluebird = function () {
    return data;
  };

  return data;
}

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

  _ramda = function () {
    return data;
  };

  return data;
}

function _objects() {
  const data = require("../objects");

  _objects = function () {
    return data;
  };

  return data;
}

function _constants() {
  const data = require("../../constants");

  _constants = function () {
    return data;
  };

  return data;
}

function _migrationHelper() {
  const data = _interopRequireDefault(require("../../migration/migration-helper"));

  _migrationHelper = function () {
    return data;
  };

  return data;
}

function _logger() {
  const data = _interopRequireDefault(require("../../logger/logger"));

  _logger = function () {
    return data;
  };

  return data;
}

let globalVerbose = false;
const refsIndex = {};
/**
 * Running migration process for scope
 * @param {string} scopeVersion - The current scope version
 * @param {Object} migratonManifest - A manifest which define what migrations to run
 * @param {BitRawObject} objects - Scope's raw objects
 * @param {boolean} verbose - print logs
 */

var _default =
/*#__PURE__*/
function () {
  var _migrate = (0, _bluebird().coroutine)(function* (scopeVersion, migratonManifest, objects, verbose = false) {
    globalVerbose = verbose;
    const migrations = (0, _migrationHelper().default)(_constants().BIT_VERSION, scopeVersion, migratonManifest, verbose);
    const result = {
      newObjects: {},
      refsToRemove: []
    };

    if (_ramda().default.isEmpty(migrations)) {
      const noMigrationMsg = 'there are no migrations to run, leaving the scope as is with no changes';

      _logger().default.debug(noMigrationMsg);

      if (verbose) console.log(noMigrationMsg); // eslint-disable-line

      return result;
    }

    _ramda().default.forEach(_runAllMigrationsForObject(migrations), objects);

    _ramda().default.forEach(_getRealObjectWithUpdatedRefs(result, refsIndex), objects);

    result.newObjects = _ramda().default.values(result.newObjects);
    return result;
  });

  function migrate(_x, _x2, _x3) {
    return _migrate.apply(this, arguments);
  }

  return migrate;
}();
/**
 * Runs all the migrations for all the versions for a given object
 * @param {VersionMigrations[]} migrations
 */


exports.default = _default;

const _runAllMigrationsForObject = migrations => rawObject => {
  // Make sure we got a rawObject (we might get a null object in case of corrupted object)
  if (!rawObject) {
    return null;
  }

  _logger().default.debug(`start updating object ${rawObject.ref} (${rawObject.id})`); // Skip Source files since we don't want the migration to run over them


  if (rawObject.type === 'Source') return null; // Add refs to index

  _addObjectRefsToIndex(refsIndex, rawObject);

  return _ramda().default.forEach(_runAllVersionMigrationsForObject(rawObject), migrations);
};
/**
 * Runs all the the migration in specific version on object
 * @param {BitRawObject} rawObject - object to run migration on
 */


const _runAllVersionMigrationsForObject = rawObject => migrations => {
  const versionNumber = Object.keys(migrations)[0];

  _logger().default.debug(`updating object ${rawObject.ref} (${rawObject.id}) to version ${versionNumber}`);

  const migrationForObjectType = migrations[versionNumber][rawObject.type]; // There is no migration for this type of object for this version

  if (!migrationForObjectType) return rawObject;
  return _ramda().default.forEach(_runOneMigrationForObject(rawObject), migrationForObjectType);
};
/**
 * Run specific migration function on an object
 * @param {BitRawObject} rawObject
 */


const _runOneMigrationForObject = rawObject => migration => {
  _logger().default.debug(`running migration: ${migration.name} on object ${rawObject.ref} (${rawObject.id})`);

  if (globalVerbose) console.log(`running migration: ${migration.name} on object ${rawObject.ref} (${rawObject.id})`);

  try {
    const migratedContent = migration.migrate(rawObject.getParsedContent());
    rawObject.parsedContent = migratedContent;
    return migratedContent;
  } catch (err) {
    _logger().default.error(`FAILED - running migration: ${migration.name} on object ${rawObject.ref} (${rawObject.id})`);

    throw err;
  }
};
/**
 * Adds all the refs from the raw object to a global index
 * To improve performence in case we need to update objet in case the id of the ref has been changed
 * @param {BitRawObject} rawObject
 */


function _addObjectRefsToIndex(index, rawObject) {
  const refs = rawObject.refs();
  refs.forEach(ref => {
    index[ref] = rawObject;
  });
}
/**
 * Update a refrence for an object and return the parsed real object
 * @param {*} index - refs index in order to update refs if needed
 * @param {*} oldRef
 * @param {*} newRef
 */


const _updateRefsForObjects = (index, oldRef, newRef) => {
  // If the object doesn't has a dependent object return null
  // This object reference won't be update anywhere
  if (!index[oldRef]) {
    _logger().default.warn(`the object ref: ${oldRef} has been updated to: ${newRef} but there is no reference to this object`);

    return null;
  }

  const realObject = index[oldRef].toRealObject();

  if (oldRef !== newRef) {
    // Get the dependent object and replace the ref to the new one
    _logger().default.debug(`replacing reference for ${realObject.id()} old ref was: ${oldRef} new ref is: ${newRef}`);

    if (globalVerbose) {
      console.log(`replacing reference for ${realObject.id()} old ref was: ${oldRef} new ref is: ${newRef}`);
    }

    realObject.replaceRef(new (_objects().Ref)(oldRef), new (_objects().Ref)(newRef));
  }

  return realObject;
};
/**
 * Get the real object and update refs if needed
 * The result will be added to the result cache
 * @param {ScopeMigrationResultCache} result - results cache
 * @param {{ [string]: BitRawObject}} index - refs index in order to update refs if needed
 */


const _getRealObjectWithUpdatedRefs = (result, index) => object => {
  // Make sure we got a rawObject (we might get a null object in case of corrupted object)
  if (!object) {
    return null;
  }

  const realObject = object.toRealObject(); // Make sure to not ovveride result we already put during the updte ref process

  if (result.newObjects[realObject.hash().hash]) return null;
  result.newObjects[realObject.hash().hash] = realObject; // Check if we need to update ref

  if (realObject.hash().hash !== object.ref) {
    result.refsToRemove.push(new (_objects().Ref)(object.ref));

    const dependentObject = _updateRefsForObjects(index, object.ref, realObject.hash().hash); // Update the dependent object only if found one


    if (dependentObject) {
      result.newObjects[dependentObject.hash().hash] = dependentObject;
    }
  }
};