"use strict";

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

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

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

var _bn = _interopRequireDefault(require("bn.js"));

var _rxjs = require("rxjs");

var _operators = require("rxjs/operators");

var _types = require("@polkadot/types");

var _util = require("@polkadot/util");

var _bestNumber = require("../chain/bestNumber");

var _eraLength = require("../session/eraLength");

var _recentlyOffline = require("./recentlyOffline");

var _drr = require("../util/drr");

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }

function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }

// groups the supplied chunks by era, i.e. { [era]: BN(total of values) }
function groupByEra(list) {
  return list.reduce((map, _ref) => {
    let {
      era,
      value
    } = _ref;
    const key = era.toString();
    map[key] = !map[key] ? value.unwrap() : map[key].add(value.unwrap());
    return map;
  }, {});
} // calculate the remining blocks in a specific unlock era


function remainingBlocks(era, eraLength, bestNumber) {
  const remaining = eraLength.mul(era).sub(bestNumber);
  return (0, _types.createType)('BlockNumber', remaining.lten(0) ? new _bn.default(0) : remaining);
}

function calculateUnlocking(stakingLedger, eraLength, bestNumber) {
  if ((0, _util.isUndefined)(stakingLedger)) {
    return undefined;
  }

  const unlockingChunks = stakingLedger.unlocking.filter((_ref2) => {
    let {
      era
    } = _ref2;
    return remainingBlocks(era.unwrap(), eraLength, bestNumber).gtn(0);
  });

  if (!unlockingChunks.length) {
    return undefined;
  } // group the Unlockchunks that have the same era and sum their values


  const groupedResult = groupByEra(unlockingChunks);
  const results = Object.entries(groupedResult).map((_ref3) => {
    let [eraString, value] = _ref3;
    return {
      value: (0, _types.createType)('Balance', value),
      remainingBlocks: remainingBlocks((0, _types.createType)('BlockNumber', eraString), eraLength, bestNumber)
    };
  });
  return results.length ? results : undefined;
}

function redeemableSum(stakingLedger, eraLength, bestNumber) {
  if ((0, _util.isUndefined)(stakingLedger)) {
    return (0, _types.createType)('Balance');
  }

  return (0, _types.createType)('Balance', stakingLedger.unlocking.reduce((total, _ref4) => {
    let {
      era,
      value
    } = _ref4;
    return remainingBlocks(era.unwrap(), eraLength, bestNumber).eqn(0) ? total.add(value.unwrap()) : total;
  }, new _bn.default(0)));
}

function unwrapSessionIds(stashId, queuedKeys, nextKeys) {
  // for 2.x we have a Vec<(ValidatorId,Keys)> of the keys
  if (Array.isArray(queuedKeys)) {
    const sessionIds = (queuedKeys.find((_ref5) => {
      let [currentId] = _ref5;
      return currentId.eq(stashId);
    }) || [undefined, []])[1];
    const nextSessionIds = nextKeys.unwrapOr([]);
    return {
      nextSessionId: nextSessionIds[0],
      nextSessionIds,
      sessionId: sessionIds[0],
      sessionIds
    };
  } // substrate 1.x


  const nextSessionIds = queuedKeys.isSome ? [queuedKeys.unwrap()] : [];
  return {
    nextSessionId: nextSessionIds[0],
    nextSessionIds,
    sessionId: nextSessionIds[0],
    sessionIds: nextSessionIds
  };
}

function retrieveMulti(api, stashId, controllerId) {
  return (0, _rxjs.combineLatest)([// TODO We really want this as part of the multi, however can only do that
  // once we drop substrate 1.x support (nulti requires values for all)
  api.query.session.nextKeys ? api.query.session.nextKeys(api.consts.session.dedupKeyPrefix, stashId) : (0, _rxjs.of)((0, _types.createType)('Option<Keys>', null)), api.queryMulti([api.query.session.queuedKeys ? [api.query.session.queuedKeys] : [api.query.session.nextKeyFor, controllerId], [api.query.staking.ledger, controllerId], [api.query.staking.nominators, stashId], [api.query.staking.payee, stashId], [api.query.staking.stakers, stashId], [api.query.staking.validators, stashId]])]);
}

function retrieveInfo(api, accountId, stashId, controllerId) {
  return (0, _rxjs.combineLatest)([(0, _eraLength.eraLength)(api)(), (0, _bestNumber.bestNumber)(api)(), (0, _recentlyOffline.recentlyOffline)(api)(), retrieveMulti(api, stashId, controllerId)]).pipe((0, _operators.map)((_ref6) => {
    let [eraLength, bestNumber, recentlyOffline, [nextKeys, [queuedKeys, _stakingLedger, [nominators], rewardDestination, stakers, [validatorPrefs]]]] = _ref6;

    const stakingLedger = _stakingLedger.unwrapOr(undefined);

    return _objectSpread({
      accountId,
      controllerId,
      nominators,
      offline: recentlyOffline[stashId.toString()],
      redeemable: redeemableSum(stakingLedger, eraLength, bestNumber),
      rewardDestination,
      stakers,
      stakingLedger,
      stashId,
      unlocking: calculateUnlocking(stakingLedger, eraLength, bestNumber),
      validatorPrefs
    }, unwrapSessionIds(stashId, queuedKeys, nextKeys));
  }));
}
/**
 * @description From a stash, retrieve the controllerId and fill in all the relevant staking details
 */


function info(api) {
  return _accountId => {
    const accountId = (0, _types.createType)('AccountId', _accountId);
    return (// NOTE For 2.x-only support, only the first path is required, therefore we
      // can replace this with `.bonded<Option<AccountId>>(accountId)` - in 2.x
      // the session.validators return the stashes (as expected)
      api.queryMulti([[api.query.staking.bonded, accountId], // try to map to controller
      [api.query.staking.ledger, accountId] // try to map to stash (1.x only)
      ]).pipe((0, _operators.switchMap)((_ref7) => {
        let [controllerId, stakingLedger] = _ref7;
        return controllerId.isSome ? retrieveInfo(api, accountId, accountId, controllerId.unwrap()) : stakingLedger.isSome ? retrieveInfo(api, accountId, stakingLedger.unwrap().stash, accountId) : (0, _rxjs.of)({
          accountId,
          nextSessionIds: [],
          sessionIds: []
        });
      }), (0, _drr.drr)())
    );
  };
}