'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var _asyncToGenerator = require('@babel/runtime/helpers/asyncToGenerator');
var _typeof = require('@babel/runtime/helpers/typeof');
var _defineProperty = require('@babel/runtime/helpers/defineProperty');
var _regeneratorRuntime = require('@babel/runtime/regenerator');
var minterjsUtil = require('minterjs-util');
var getCommissionPrice = require('./get-commission-price.js');
var getPoolInfo = require('./get-pool-info.js');
var replaceCoin = require('./replace-coin.js');
var index = require('../tx-data/index.js');
var tx = require('../tx.js');

function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); 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 = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
// import {isCoinId, validateUint} from '../utils.js';

/**
 * @enum {string}
 */
var FEE_PRECISION_SETTING = {
  // value may be precise, imprecise, or even omitted
  AUTO: 'auto',
  // precise value is required
  PRECISE: 'precise',
  // some value is required but it can be imprecise
  IMPRECISE: 'imprecise',
  OMIT: 'omit'
};
var PRECISION = FEE_PRECISION_SETTING;

/**
 * @param {MinterApiInstance} apiInstance
 * @param {import('axios').AxiosRequestConfig} [factoryAxiosOptions]
 * @param {import('axios').AxiosRequestConfig} [factoryExtraAxiosOptions]
 * @return {EstimateTxCommissionInstance}
 */
function EstimateTxCommission(apiInstance, factoryAxiosOptions, factoryExtraAxiosOptions) {
  var getCommissionPrice$1 = getCommissionPrice(apiInstance, factoryExtraAxiosOptions);
  var getPoolInfo$1 = getPoolInfo(apiInstance, factoryExtraAxiosOptions);
  var replaceCoinSymbol = replaceCoin.ReplaceCoinSymbol(apiInstance, factoryExtraAxiosOptions);

  /**
   * @typedef {MinterFeeEstimationDirect|MinterFeeEstimationCalculate} MinterFeeEstimation
   */
  /**
   * @typedef {Function} EstimateTxCommissionInstance
   * @param {TxParams|string} txParams
   * @param {object} [options]
   * @param {FEE_PRECISION_SETTING} [options.needGasCoinFee]
   * @param {FEE_PRECISION_SETTING} [options.needBaseCoinFee]
   * @param {FEE_PRECISION_SETTING} [options.needPriceCoinFee]
   * @param {boolean} [options.loose] - DEPRECATED
   * @param {boolean} [options.direct] - DEPRECATED
   * @param {import('axios').AxiosRequestConfig} [axiosOptions] - for main request (estimation)
   * @param {import('axios').AxiosRequestConfig} [extraAxiosOptions] - for secondary requests (commission price data, coin IDs, and pool info)
   * @return {Promise<MinterFeeEstimation>}
   */
  return function estimateTxCommission(txParams) {
    var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
      _ref$needGasCoinFee = _ref.needGasCoinFee,
      needGasCoinFee = _ref$needGasCoinFee === void 0 ? PRECISION.AUTO : _ref$needGasCoinFee,
      _ref$needBaseCoinFee = _ref.needBaseCoinFee,
      needBaseCoinFee = _ref$needBaseCoinFee === void 0 ? PRECISION.AUTO : _ref$needBaseCoinFee,
      _ref$needPriceCoinFee = _ref.needPriceCoinFee,
      needPriceCoinFee = _ref$needPriceCoinFee === void 0 ? PRECISION.AUTO : _ref$needPriceCoinFee,
      loose = _ref.loose,
      direct = _ref.direct;
    var axiosOptions = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;
    var extraAxiosOptions = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : undefined;
    axiosOptions = _objectSpread(_objectSpread({}, factoryAxiosOptions), axiosOptions);
    if (direct !== undefined) {
      // eslint-disable-next-line no-console
      console.warn('`direct` option in `estimateTxCommission` is deprecated, use `needGasCoinFee`, `needBaseCoinFee`, and `needPriceCoinFee` options instead');
      needGasCoinFee = direct ? PRECISION.PRECISE : PRECISION.IMPRECISE;
      needBaseCoinFee = direct ? PRECISION.OMIT : PRECISION.IMPRECISE;
      needPriceCoinFee = direct ? PRECISION.OMIT : PRECISION.PRECISE;
    }
    if (loose !== undefined) {
      // eslint-disable-next-line no-console
      console.warn('`loose` option in `estimateTxCommission` is deprecated, use `needGasCoinFee`, `needBaseCoinFee`, and `needPriceCoinFee` options instead');
      needGasCoinFee = !loose ? PRECISION.PRECISE : PRECISION.IMPRECISE;
      needBaseCoinFee = !loose ? PRECISION.OMIT : PRECISION.IMPRECISE;
      needPriceCoinFee = !loose ? PRECISION.OMIT : PRECISION.PRECISE;
    }
    var paramsPromise;
    if (_typeof(txParams) === 'object') {
      /*
      if (loose) {
          paramsPromise = getCoinId(txParams.gasCoin || 0, txParams.chainId, extraAxiosOptions)
              .then((coinId) => {
                  validateUint(coinId, 'gasCoin');
                  return {
                      ...txParams,
                      gasCoin: coinId,
                  };
              });
      } else {
      */
      // @TODO some fields of tx data can dropped, because they don't affect fee, it will reduce coin id requests and make estimation requests more cacheable
      paramsPromise = replaceCoinSymbol(txParams, extraAxiosOptions);
      // }
    } else {
      paramsPromise = Promise.resolve(txParams);
    }
    return paramsPromise.then(function (updatedTxParams) {
      if (_typeof(updatedTxParams) !== 'object') {
        return estimateFeeDirect(updatedTxParams, axiosOptions);
      } else {
        return estimateFeeCalculate(updatedTxParams, {
          needGasCoinFee: needGasCoinFee,
          needBaseCoinFee: needBaseCoinFee,
          needPriceCoinFee: needPriceCoinFee,
          axiosOptions: axiosOptions,
          extraAxiosOptions: extraAxiosOptions
        });
      }
    });
  };

  /**
   * @typedef {{commission: number|string}} MinterFeeEstimationDirect
   */
  /**
   * @param {string|TxParams} txParams
   * @param {import('axios').AxiosRequestConfig} [axiosOptions]
   * @return {Promise<MinterFeeEstimationDirect>}
   */
  function estimateFeeDirect(txParams, axiosOptions) {
    if (!txParams) {
      return Promise.reject(new Error('Transaction not specified'));
    }
    var tx$1;
    if (typeof txParams === 'string') {
      tx$1 = txParams;
    } else {
      tx$1 = tx.prepareTx(_objectSpread(_objectSpread({}, txParams), {}, {
        data: index.fillDefaultData(txParams.type, txParams.data)
      }), {
        disableValidation: true,
        disableDecorationParams: true
      }).serializeToString();
    }
    return apiInstance.get("estimate_tx_commission/".concat(tx$1), axiosOptions).then(function (response) {
      return {
        commission: minterjsUtil.convertFromPip(response.data.commission)
      };
    });
  }

  /**
   * @typedef {MinterFeeEstimationDirect&{baseCoinCommission: number|string, priceCoinCommission: number|string, commissionPriceData: CommissionPriceData}} MinterFeeEstimationCalculate
   */
  /**
   * @param {TxParams} txParams
   * @param {object} [options]
   * @param {FEE_PRECISION_SETTING} [options.needGasCoinFee]
   * @param {FEE_PRECISION_SETTING} [options.needBaseCoinFee]
   * @param {FEE_PRECISION_SETTING} [options.needPriceCoinFee]
   * @param {import('axios').AxiosRequestConfig} [options.axiosOptions]
   * @param {import('axios').AxiosRequestConfig} [options.extraAxiosOptions] - applied to secondary requests
   * @return {Promise<MinterFeeEstimationCalculate>}
   */
  function estimateFeeCalculate(_x, _x2) {
    return _estimateFeeCalculate.apply(this, arguments);
  }
  function _estimateFeeCalculate() {
    _estimateFeeCalculate = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2(txParams, _ref2) {
      var needGasCoinFee, needBaseCoinFee, needPriceCoinFee, axiosOptions, extraAxiosOptions, commissionPriceData, sameGasAndBaseCoins, sameGasAndPriceCoins, samePriceAndBaseCoins, gasDependsOnBase, baseDependsOnGas, priceCoinFee, baseCoinFee, fee, _yield$estimateFeeDir, commission;
      return _regeneratorRuntime.wrap(function _callee2$(_context2) {
        while (1) switch (_context2.prev = _context2.next) {
          case 0:
            needGasCoinFee = _ref2.needGasCoinFee, needBaseCoinFee = _ref2.needBaseCoinFee, needPriceCoinFee = _ref2.needPriceCoinFee, axiosOptions = _ref2.axiosOptions, extraAxiosOptions = _ref2.extraAxiosOptions;
            if (!(!txParams || _typeof(txParams) !== 'object')) {
              _context2.next = 3;
              break;
            }
            throw new TypeError('Invalid txParams');
          case 3:
            if (needPriceCoinFee === PRECISION.AUTO) {
              needPriceCoinFee = PRECISION.OMIT;
            }
            if (needBaseCoinFee === PRECISION.AUTO) {
              needBaseCoinFee = PRECISION.OMIT;
            }
            if (needGasCoinFee === PRECISION.AUTO) {
              needGasCoinFee = PRECISION.PRECISE;
            }
            if (!(needPriceCoinFee !== PRECISION.OMIT)) {
              _context2.next = 12;
              break;
            }
            _context2.next = 9;
            return getCommissionPrice$1(extraAxiosOptions);
          case 9:
            _context2.t0 = _context2.sent;
            _context2.next = 13;
            break;
          case 12:
            _context2.t0 = undefined;
          case 13:
            commissionPriceData = _context2.t0;
            // coins may be same only if they both defined
            sameGasAndBaseCoins = isGasCoinSameAsBaseCoin(txParams.gasCoin);
            sameGasAndPriceCoins = isGasCoinSameAsPriceCoin(txParams.gasCoin, commissionPriceData);
            samePriceAndBaseCoins = isPriceCoinSameAsBaseCoin(commissionPriceData);
            gasDependsOnBase = sameGasAndBaseCoins && needGasCoinFee === PRECISION.IMPRECISE && needBaseCoinFee === PRECISION.IMPRECISE && commissionPriceData;
            baseDependsOnGas = sameGasAndBaseCoins && needGasCoinFee === PRECISION.PRECISE; // priceCoin
            priceCoinFee = function () {
              // OMIT
              if (!commissionPriceData) {
                return undefined;
              }
              // PRECISE
              var feePrice = new minterjsUtil.FeePrice(commissionPriceData);
              return feePrice.getFeeValue(txParams.type, getFeePriceOptionsFromTxParams(txParams, true));
            }(); // baseCoin
            _context2.next = 22;
            return _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
              var priceCoinPool;
              return _regeneratorRuntime.wrap(function _callee$(_context) {
                while (1) switch (_context.prev = _context.next) {
                  case 0:
                    if (!samePriceAndBaseCoins) {
                      _context.next = 2;
                      break;
                    }
                    return _context.abrupt("return", priceCoinFee);
                  case 2:
                    if (!baseDependsOnGas) {
                      _context.next = 4;
                      break;
                    }
                    return _context.abrupt("return", undefined);
                  case 4:
                    if (!(needBaseCoinFee === PRECISION.OMIT)) {
                      _context.next = 6;
                      break;
                    }
                    return _context.abrupt("return", undefined);
                  case 6:
                    if (!(needBaseCoinFee === PRECISION.IMPRECISE && commissionPriceData)) {
                      _context.next = 11;
                      break;
                    }
                    _context.next = 9;
                    return getPoolInfo$1(0, commissionPriceData.coin.id, extraAxiosOptions);
                  case 9:
                    priceCoinPool = _context.sent;
                    return _context.abrupt("return", getBaseCoinAmountFromPool(priceCoinFee, priceCoinPool));
                  case 11:
                    if (!(needBaseCoinFee === PRECISION.IMPRECISE && !commissionPriceData)) {
                      _context.next = 13;
                      break;
                    }
                    throw new Error('base coin fee imprecise estimation with omitted price coin is not implemented yet');
                  case 13:
                    throw new Error('base coin fee precise estimation not implemented yet');
                  case 14:
                  case "end":
                    return _context.stop();
                }
              }, _callee);
            }))();
          case 22:
            baseCoinFee = _context2.sent;
            if (!sameGasAndPriceCoins) {
              _context2.next = 27;
              break;
            }
            // PRECISE
            // actual fee may be few pips less than priceCoinFee, assume it's not worth of extra http request
            fee = priceCoinFee;
            _context2.next = 37;
            break;
          case 27:
            if (!gasDependsOnBase) {
              _context2.next = 31;
              break;
            }
            // IMPRECISE
            fee = baseCoinFee;
            _context2.next = 37;
            break;
          case 31:
            if (!(needGasCoinFee !== PRECISION.OMIT)) {
              _context2.next = 37;
              break;
            }
            _context2.next = 34;
            return estimateFeeDirect(txParams, axiosOptions);
          case 34:
            _yield$estimateFeeDir = _context2.sent;
            commission = _yield$estimateFeeDir.commission;
            fee = commission;
          case 37:
            if (baseDependsOnGas) {
              baseCoinFee = fee;
            }
            return _context2.abrupt("return", {
              commission: fee,
              baseCoinCommission: baseCoinFee,
              priceCoinCommission: priceCoinFee,
              commissionPriceData: commissionPriceData
            });
          case 39:
          case "end":
            return _context2.stop();
        }
      }, _callee2);
    }));
    return _estimateFeeCalculate.apply(this, arguments);
  }
}

/**
 * @param {CommissionPriceData} commissionPriceData
 * @return {boolean}
 */
function isPriceCoinSameAsBaseCoin(commissionPriceData) {
  return Number.parseInt(commissionPriceData === null || commissionPriceData === void 0 ? void 0 : commissionPriceData.coin.id, 10) === 0;
}

/**
 * @param {number|string} gasCoinId
 * @param {CommissionPriceData} commissionPriceData
 * @return {boolean}
 */
function isGasCoinSameAsPriceCoin(gasCoinId, commissionPriceData) {
  return Number.parseInt(gasCoinId, 10) === Number.parseInt(commissionPriceData === null || commissionPriceData === void 0 ? void 0 : commissionPriceData.coin.id, 10);
}

/**
 * @param {number|string} gasCoinId
 * @return {boolean}
 */
function isGasCoinSameAsBaseCoin(gasCoinId) {
  return Number.parseInt(gasCoinId, 10) === 0;
}

/**
 *
 * @param {number|string} priceCoinAmount
 * @param {PoolInfo} pool
 * @return {string|number}
 */
function getBaseCoinAmountFromPool(priceCoinAmount, pool) {
  // amount of base coin in pool
  var reserveBase = new minterjsUtil.Big(pool.amount0);
  // amount of price coin in pool
  var reservePrice = new minterjsUtil.Big(pool.amount1);
  // amount of price coin in pool
  new minterjsUtil.Big(priceCoinAmount);

  // @see https://github.com/MinterTeam/minter-go-node/blob/6e44d5691c9df1a9c725d0f52c5921e8523c7f18/coreV2/state/swap/swap.go#L642
  // reserveBase - (reservePrice * reserveBase) / (priceCoinAmount * 0.997 + reservePrice)
  var result = reserveBase.minus(reservePrice.times(reserveBase).div(new minterjsUtil.Big(priceCoinAmount).times(0.997).plus(reservePrice)));

  // received amount from pool rounds down, spent amount to pool rounds up
  // round down
  result = result.round(18, minterjsUtil.BIG_ROUND_DOWN);
  return result;
}

/**
 * @param {TxParams} txParams
 * @param {boolean} [disableValidation]
 * @return FeePriceOptions
 */
function getFeePriceOptionsFromTxParams(txParams, disableValidation) {
  var _txParams$data;
  var txType = txParams.type;
  if (!txType) {
    throw new Error('Tx `type` not specified');
  }
  var isTickerType = txType === minterjsUtil.TX_TYPE.CREATE_COIN || txType === minterjsUtil.TX_TYPE.CREATE_TOKEN;
  var coinSymbol = isTickerType ? (_txParams$data = txParams.data) === null || _txParams$data === void 0 ? void 0 : _txParams$data.symbol : undefined;
  if (isTickerType && !coinSymbol && !disableValidation) {
    throw new Error('`symbol` not specified for ticker creation tx');
  }
  var deltaItemCount;
  if (txType === minterjsUtil.TX_TYPE.BUY_SWAP_POOL || txType === minterjsUtil.TX_TYPE.SELL_SWAP_POOL || txType === minterjsUtil.TX_TYPE.SELL_ALL_SWAP_POOL) {
    var _txParams$data2, _txParams$data2$coins;
    var coinCount = (_txParams$data2 = txParams.data) === null || _txParams$data2 === void 0 ? void 0 : (_txParams$data2$coins = _txParams$data2.coins) === null || _txParams$data2$coins === void 0 ? void 0 : _txParams$data2$coins.length;
    if (!coinCount && !disableValidation) {
      throw new Error('Invalid `coins` field in swap pool tx');
    }
    // count of pools
    deltaItemCount = coinCount - 1;
  }
  if (txType === minterjsUtil.TX_TYPE.MULTISEND) {
    var _txParams$data3, _txParams$data3$list;
    // count of recipients
    deltaItemCount = (_txParams$data3 = txParams.data) === null || _txParams$data3 === void 0 ? void 0 : (_txParams$data3$list = _txParams$data3.list) === null || _txParams$data3$list === void 0 ? void 0 : _txParams$data3$list.length;
    if (!deltaItemCount && !disableValidation) {
      throw new Error('Invalid `list` field in multisend tx');
    }
  }
  return {
    payload: txParams.payload,
    coinSymbol: coinSymbol,
    deltaItemCount: deltaItemCount,
    fallbackOnInvalidInput: disableValidation
  };
}

exports.FEE_PRECISION_SETTING = FEE_PRECISION_SETTING;
exports.default = EstimateTxCommission;
