'use strict';

var ethers = require('ethers');
var invariant = require('tiny-invariant');
var QueryParams = require('./QueryParams-015a02c9.cjs.prod.js');
var index = require('./index-5adc67b5.cjs.prod.js');
var fetchCurrencyValue = require('./fetchCurrencyValue-18a2025f.cjs.prod.js');

function _interopDefault (e) { return e && e.__esModule ? e : { 'default': e }; }

function _interopNamespace(e) {
  if (e && e.__esModule) return e;
  var n = Object.create(null);
  if (e) {
    Object.keys(e).forEach(function (k) {
      if (k !== 'default') {
        var d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: function () { return e[k]; }
        });
      }
    });
  }
  n["default"] = e;
  return Object.freeze(n);
}

var invariant__default = /*#__PURE__*/_interopDefault(invariant);

/**
 * This method checks if the given token is approved for the transferrerContractAddress contract.
 * This is particularly useful for contracts that need to transfer NFTs on the users' behalf
 *
 * @internal
 * @param provider - The connected provider
 * @param transferrerContractAddress - The address of the marketplace contract
 * @param assetContract - The address of the asset contract.
 * @param tokenId - The token id of the token.
 * @param owner - The address of the account that owns the token.
 * @returns  True if the transferrerContractAddress is approved on the token, false otherwise.
 */
async function isTokenApprovedForTransfer(provider, transferrerContractAddress, assetContract, tokenId, owner) {
  try {
    const ERC165Abi = (await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('@thirdweb-dev/contracts-js/dist/abis/IERC165.json')); })).default;
    const erc165 = new ethers.Contract(assetContract, ERC165Abi, provider);
    const [isERC721, isERC1155] = await Promise.all([erc165.supportsInterface(QueryParams.InterfaceId_IERC721), erc165.supportsInterface(QueryParams.InterfaceId_IERC1155)]);
    if (isERC721) {
      const ERC721Abi = (await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('@thirdweb-dev/contracts-js/dist/abis/IERC721.json')); })).default;
      const asset = new ethers.Contract(assetContract, ERC721Abi, provider);
      const approved = await asset.isApprovedForAll(owner, transferrerContractAddress);
      if (approved) {
        return true;
      }

      // Handle reverts in case of non-existent tokens
      let approvedAddress;
      try {
        approvedAddress = await asset.getApproved(tokenId);
      } catch (e) {}
      return approvedAddress?.toLowerCase() === transferrerContractAddress.toLowerCase();
    } else if (isERC1155) {
      const ERC1155Abi = (await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('@thirdweb-dev/contracts-js/dist/abis/IERC1155.json')); })).default;
      const asset = new ethers.Contract(assetContract, ERC1155Abi, provider);
      return await asset.isApprovedForAll(owner, transferrerContractAddress);
    } else {
      console.error("Contract does not implement ERC 1155 or ERC 721.");
      return false;
    }
  } catch (err) {
    console.error("Failed to check if token is approved", err);
    return false;
  }
}

/**
 * Checks if the marketplace is approved to make transfers on the assetContract
 * If not, it tries to set the approval.
 * @param contractWrapper - The contract wrapper to use
 * @param marketplaceAddress - The address of the marketplace contract
 * @param assetContract - The address of the asset contract.
 * @param tokenId - The token id of the token.
 * @param from - The address of the account that owns the token.
 */
async function handleTokenApproval(contractWrapper, marketplaceAddress, assetContract, tokenId, from) {
  const ERC165Abi = (await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('@thirdweb-dev/contracts-js/dist/abis/IERC165.json')); })).default;
  const erc165 = new index.ContractWrapper(contractWrapper.getSignerOrProvider(), assetContract, ERC165Abi, contractWrapper.options, contractWrapper.storage);
  const [isERC721, isERC1155] = await Promise.all([erc165.read("supportsInterface", [QueryParams.InterfaceId_IERC721]), erc165.read("supportsInterface", [QueryParams.InterfaceId_IERC1155])]);
  // check for token approval
  if (isERC721) {
    const ERC721Abi = (await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('@thirdweb-dev/contracts-js/dist/abis/IERC721.json')); })).default;
    const asset = new index.ContractWrapper(contractWrapper.getSignerOrProvider(), assetContract, ERC721Abi, contractWrapper.options, contractWrapper.storage);
    const approved = await asset.read("isApprovedForAll", [from, marketplaceAddress]);
    if (!approved) {
      const isTokenApproved = (await asset.read("getApproved", [tokenId])).toLowerCase() === marketplaceAddress.toLowerCase();
      if (!isTokenApproved) {
        await asset.sendTransaction("setApprovalForAll", [marketplaceAddress, true]);
      }
    }
  } else if (isERC1155) {
    const ERC1155Abi = (await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespace(require('@thirdweb-dev/contracts-js/dist/abis/IERC1155.json')); })).default;
    const asset = new index.ContractWrapper(contractWrapper.getSignerOrProvider(), assetContract, ERC1155Abi, contractWrapper.options, contractWrapper.storage);
    const approved = await asset.read("isApprovedForAll", [from, marketplaceAddress]);
    if (!approved) {
      await asset.sendTransaction("setApprovalForAll", [marketplaceAddress, true]);
    }
  } else {
    throw Error("Contract must implement ERC 1155 or ERC 721.");
  }
}

/**
 * Used to verify fields in new listing.
 * @internal
 */
// TODO this should be done in zod
function validateNewListingParam(param) {
  invariant__default["default"](param.assetContractAddress !== undefined && param.assetContractAddress !== null, "Asset contract address is required");
  invariant__default["default"](param.buyoutPricePerToken !== undefined && param.buyoutPricePerToken !== null, "Buyout price is required");
  invariant__default["default"](param.listingDurationInSeconds !== undefined && param.listingDurationInSeconds !== null, "Listing duration is required");
  invariant__default["default"](param.startTimestamp !== undefined && param.startTimestamp !== null, "Start time is required");
  invariant__default["default"](param.tokenId !== undefined && param.tokenId !== null, "Token ID is required");
  invariant__default["default"](param.quantity !== undefined && param.quantity !== null, "Quantity is required");
  switch (param.type) {
    case "NewAuctionListing":
      {
        invariant__default["default"](param.reservePricePerToken !== undefined && param.reservePricePerToken !== null, "Reserve price is required");
      }
  }
}

/**
 * Maps a contract offer to the strict interface
 *
 * @internal
 * @param offer - The offer to map
 * @returns  An `Offer` object
 */
async function mapOffer(provider, listingId, offer) {
  return {
    quantity: offer.quantityDesired,
    pricePerToken: offer.pricePerToken,
    currencyContractAddress: offer.currency,
    buyerAddress: offer.offeror,
    quantityDesired: offer.quantityWanted,
    currencyValue: await fetchCurrencyValue.fetchCurrencyValue(provider, offer.currency, offer.quantityWanted.mul(offer.pricePerToken)),
    listingId
  };
}
function isWinningBid(winningPrice, newBidPrice, bidBuffer) {
  bidBuffer = ethers.BigNumber.from(bidBuffer);
  winningPrice = ethers.BigNumber.from(winningPrice);
  newBidPrice = ethers.BigNumber.from(newBidPrice);
  if (winningPrice.eq(ethers.BigNumber.from(0))) {
    return false;
  }
  const buffer = newBidPrice.sub(winningPrice).mul(index.MAX_BPS).div(winningPrice);
  return buffer.gte(bidBuffer);
}
async function getAllInBatches(start, end, fn) {
  const batches = [];
  while (end - start > QueryParams.DEFAULT_QUERY_ALL_COUNT) {
    batches.push(fn(start, start + QueryParams.DEFAULT_QUERY_ALL_COUNT - 1));
    start += QueryParams.DEFAULT_QUERY_ALL_COUNT;
  }
  batches.push(fn(start, end - 1));
  return await Promise.all(batches);
}

exports.getAllInBatches = getAllInBatches;
exports.handleTokenApproval = handleTokenApproval;
exports.isTokenApprovedForTransfer = isTokenApprovedForTransfer;
exports.isWinningBid = isWinningBid;
exports.mapOffer = mapOffer;
exports.validateNewListingParam = validateNewListingParam;
