import { constants, utils, BigNumber } from 'ethers';
import { bR as SnapshotInputSchema, bS as SnapshotEntryWithProofSchema, bT as SnapshotSchema, cx as ContractWrapper, bb as BigNumberSchema, a$ as NATIVE_TOKEN_ADDRESS, cB as AmountSchema, cF as QuantitySchema, bc as BigNumberishSchema, cG as BytesLikeSchema, s as DuplicateLeafsError, bf as AddressOrEnsSchema, be as AddressSchema, cH as BasisPointsSchema } from './index-a68568e5.esm.js';
import { f as fetchCurrencyMetadata, i as isNativeToken, a as fetchCurrencyValue } from './fetchCurrencyValue-a8809041.esm.js';
import { n as normalizePriceValue } from './normalizePriceValue-3a2008ed.esm.js';
import { MerkleTree } from '@thirdweb-dev/merkletree';
import { S as StartDateSchema, E as EndDateSchema } from './assertEnabled-3c31e52e.esm.js';
import { z } from 'zod';
import { N as NFTInputOrUriSchema } from './setErc20Allowance-78f491f5.esm.js';
import { v4 } from 'uuid';

function abstractContractModelToLegacy(model) {
  return {
    startTimestamp: model.startTimestamp,
    maxClaimableSupply: model.maxClaimableSupply,
    supplyClaimed: model.supplyClaimed,
    merkleRoot: model.merkleRoot,
    pricePerToken: model.pricePerToken,
    currency: model.currency,
    quantityLimitPerTransaction: model.maxClaimablePerWallet,
    waitTimeInSecondsBetweenClaims: model.waitTimeInSecondsBetweenClaims || 0
  };
}

function abstractContractModelToNew(model) {
  return {
    startTimestamp: model.startTimestamp,
    maxClaimableSupply: model.maxClaimableSupply,
    supplyClaimed: model.supplyClaimed,
    merkleRoot: model.merkleRoot,
    pricePerToken: model.pricePerToken,
    currency: model.currency,
    quantityLimitPerWallet: model.maxClaimablePerWallet,
    metadata: model.metadata || ""
  };
}

/**
 * @internal
 * @param quantity - The quantity to convert
 * @param tokenDecimals - The token decimals to use
 */
function convertQuantityToBigNumber(quantity, tokenDecimals) {
  if (quantity === "unlimited") {
    return constants.MaxUint256;
  } else {
    return utils.parseUnits(quantity, tokenDecimals);
  }
}

async function parseSnapshotInputs(inputs) {
  const chunkSize = 25000;
  const chunks = Array.from({
    length: Math.ceil(inputs.length / chunkSize)
  }, (_, i) => inputs.slice(i * chunkSize, i * chunkSize + chunkSize));
  const results = [];
  const parsedChunks = await Promise.all(chunks.map(chunk => SnapshotInputSchema.parseAsync(chunk)));
  for (const chunk of parsedChunks) {
    results.push(...chunk);
  }
  return results;
}

// shard using the first 2 hex character of the address
// this splits the merkle tree into 256 shards
// shard files will be 00.json, 01.json, 02.json, ..., ff.json
const SHARD_NYBBLES = 2;
let SnapshotFormatVersion = /*#__PURE__*/function (SnapshotFormatVersion) {
  SnapshotFormatVersion[SnapshotFormatVersion["V1"] = 1] = "V1";
  SnapshotFormatVersion[SnapshotFormatVersion["V2"] = 2] = "V2";
  return SnapshotFormatVersion;
}({}); // address, maxClaimable, price, currencyAddress
class ShardedMerkleTree {
  constructor(storage, baseUri, originalEntriesUri, shardNybbles, tokenDecimals) {
    this.storage = storage;
    this.shardNybbles = shardNybbles;
    this.baseUri = baseUri;
    this.originalEntriesUri = originalEntriesUri;
    this.tokenDecimals = tokenDecimals;
    this.shards = {};
    this.trees = {};
  }
  static async fromUri(uri, storage) {
    try {
      const shardedMerkleTreeInfo = await storage.downloadJSON(uri);
      if (shardedMerkleTreeInfo.isShardedMerkleTree) {
        return ShardedMerkleTree.fromShardedMerkleTreeInfo(shardedMerkleTreeInfo, storage);
      }
    } catch (e) {
      return undefined;
    }
  }
  static async fromShardedMerkleTreeInfo(info, storage) {
    return new ShardedMerkleTree(storage, info.baseUri, info.originalEntriesUri, info.shardNybbles, info.tokenDecimals);
  }
  static hashEntry(entry, tokenDecimals, currencyDecimals, snapshotFormatVersion) {
    switch (snapshotFormatVersion) {
      case SnapshotFormatVersion.V1:
        return utils.solidityKeccak256(["address", "uint256"], [entry.address, convertQuantityToBigNumber(entry.maxClaimable, tokenDecimals)]);
      case SnapshotFormatVersion.V2:
        return utils.solidityKeccak256(["address", "uint256", "uint256", "address"], [entry.address, convertQuantityToBigNumber(entry.maxClaimable, tokenDecimals), convertQuantityToBigNumber(entry.price || "unlimited", currencyDecimals), entry.currencyAddress || constants.AddressZero]);
    }
  }
  static async fetchAndCacheDecimals(cache, provider, currencyAddress) {
    if (!currencyAddress) {
      return 18;
    }
    // cache decimals for each currency to avoid refetching for every address
    let currencyDecimals = cache[currencyAddress];
    if (currencyDecimals === undefined) {
      const currencyMetadata = await fetchCurrencyMetadata(provider, currencyAddress);
      currencyDecimals = currencyMetadata.decimals;
      cache[currencyAddress] = currencyDecimals;
    }
    return currencyDecimals;
  }
  static async buildAndUpload(snapshotInput, tokenDecimals, provider, storage, snapshotFormatVersion) {
    let shardNybbles = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : SHARD_NYBBLES;
    const inputs = await parseSnapshotInputs(snapshotInput);

    // TODO Could also derive shardNybbles from input size
    const shards = {};
    for (const snapshotEntry of inputs) {
      const shard = snapshotEntry.address.slice(2, 2 + shardNybbles).toLowerCase();
      if (shards[shard] === undefined) {
        shards[shard] = [];
      }
      shards[shard].push(snapshotEntry);
    }
    const currencyDecimalMap = {};
    // create shard => subtree root map
    const subTrees = await Promise.all(Object.entries(shards).map(async _ref => {
      let [shard, entries] = _ref;
      return [shard, new MerkleTree(await Promise.all(entries.map(async entry => {
        // cache decimals for each currency to avoid refetching for every address
        const currencyDecimals = await ShardedMerkleTree.fetchAndCacheDecimals(currencyDecimalMap, provider, entry.currencyAddress);
        return ShardedMerkleTree.hashEntry(entry, tokenDecimals, currencyDecimals, snapshotFormatVersion);
      })), utils.keccak256, {
        sort: true
      }).getHexRoot()];
    }));
    const roots = Object.fromEntries(subTrees);
    // create master tree from shard => subtree root map
    const tree = new MerkleTree(Object.values(roots), utils.keccak256, {
      sort: true
    });
    const shardsToUpload = [];
    for (const [shardId, entries] of Object.entries(shards)) {
      const data = {
        proofs: tree.getProof(roots[shardId]).map(value => "0x" + value.data.toString("hex")),
        entries
      };
      shardsToUpload.push({
        data: JSON.stringify(data),
        name: `${shardId}.json`
      });
    }
    const uris = await storage.uploadBatch(shardsToUpload);
    const baseUri = uris[0].slice(0, uris[0].lastIndexOf("/"));
    const originalEntriesUri = await storage.upload(inputs);
    const shardedMerkleInfo = {
      merkleRoot: tree.getHexRoot(),
      baseUri,
      originalEntriesUri,
      shardNybbles,
      tokenDecimals,
      isShardedMerkleTree: true
    };
    const masterUri = await storage.upload(shardedMerkleInfo);
    return {
      shardedMerkleInfo,
      uri: masterUri
    };
  }
  async getProof(address, provider, snapshotFormatVersion) {
    const shardId = address.slice(2, 2 + this.shardNybbles).toLowerCase();
    let shard = this.shards[shardId];
    const currencyDecimalMap = {};
    if (shard === undefined) {
      try {
        const uri = this.baseUri.endsWith("/") ? this.baseUri : `${this.baseUri}/`;
        shard = this.shards[shardId] = await this.storage.downloadJSON(`${uri}${shardId}.json`);
        const hashedEntries = await Promise.all(shard.entries.map(async entry => {
          // cache decimals for each currency to avoid refetching for every address
          const currencyDecimals = await ShardedMerkleTree.fetchAndCacheDecimals(currencyDecimalMap, provider, entry.currencyAddress);
          return ShardedMerkleTree.hashEntry(entry, this.tokenDecimals, currencyDecimals, snapshotFormatVersion);
        }));
        this.trees[shardId] = new MerkleTree(hashedEntries, utils.keccak256, {
          sort: true
        });
      } catch (e) {
        return null;
      }
    }
    const entry = shard.entries.find(i => i.address.toLowerCase() === address.toLowerCase());
    if (!entry) {
      return null;
    }
    const currencyDecimals = await ShardedMerkleTree.fetchAndCacheDecimals(currencyDecimalMap, provider, entry.currencyAddress);
    const leaf = ShardedMerkleTree.hashEntry(entry, this.tokenDecimals, currencyDecimals, snapshotFormatVersion);
    const proof = this.trees[shardId].getProof(leaf).map(i => "0x" + i.data.toString("hex"));
    return SnapshotEntryWithProofSchema.parseAsync({
      ...entry,
      proof: proof.concat(shard.proofs)
    });
  }
  async getAllEntries() {
    try {
      return await this.storage.downloadJSON(this.originalEntriesUri);
    } catch (e) {
      console.warn("Could not fetch original snapshot entries", e);
      return [];
    }
  }
}

/**
 * @internal
 */
async function fetchSnapshotEntryForAddress(address, merkleRoot, merkleMetadata, provider, storage, snapshotFormatVersion) {
  if (!merkleMetadata) {
    return null;
  }
  const snapshotUri = merkleMetadata[merkleRoot];
  if (snapshotUri) {
    const raw = await storage.downloadJSON(snapshotUri);
    if (raw.isShardedMerkleTree && raw.merkleRoot === merkleRoot) {
      const merkleTree = await ShardedMerkleTree.fromShardedMerkleTreeInfo(raw, storage);
      return await merkleTree.getProof(address, provider, snapshotFormatVersion);
    }
    // legacy non-sharded, just fetch it all and filter out
    const snapshotData = await SnapshotSchema.parseAsync(raw);
    if (merkleRoot === snapshotData.merkleRoot) {
      return snapshotData.claims.find(c => c.address.toLowerCase() === address.toLowerCase()) || null;
    }
  }
  return null;
}

function legacyContractModelToAbstract(model) {
  return {
    startTimestamp: model.startTimestamp,
    maxClaimableSupply: model.maxClaimableSupply,
    supplyClaimed: model.supplyClaimed,
    merkleRoot: model.merkleRoot.toString(),
    pricePerToken: model.pricePerToken,
    currency: model.currency,
    maxClaimablePerWallet: model.quantityLimitPerTransaction,
    waitTimeInSecondsBetweenClaims: model.waitTimeInSecondsBetweenClaims
  };
}

function newContractModelToAbstract(model) {
  return {
    startTimestamp: model.startTimestamp,
    maxClaimableSupply: model.maxClaimableSupply,
    supplyClaimed: model.supplyClaimed,
    merkleRoot: model.merkleRoot.toString(),
    pricePerToken: model.pricePerToken,
    currency: model.currency,
    maxClaimablePerWallet: model.quantityLimitPerWallet,
    waitTimeInSecondsBetweenClaims: 0,
    metadata: model.metadata
  };
}

/**
 * @internal
 */
async function approveErc20Allowance(contractToApprove, currencyAddress, price, quantity, tokenDecimals) {
  const signer = contractToApprove.getSigner();
  const provider = contractToApprove.getProvider();
  const ERC20Abi = (await import('@thirdweb-dev/contracts-js/dist/abis/IERC20.json')).default;
  const erc20 = new ContractWrapper(signer || provider, currencyAddress, ERC20Abi, contractToApprove.options, contractToApprove.storage);
  const owner = await contractToApprove.getSignerAddress();
  const spender = contractToApprove.address;
  const allowance = await erc20.read("allowance", [owner, spender]);
  const totalPrice = BigNumber.from(price).mul(BigNumber.from(quantity)).div(utils.parseUnits("1", tokenDecimals));
  if (allowance.lt(totalPrice)) {
    await erc20.sendTransaction("approve", [spender, allowance.add(totalPrice)]);
  }
}

/**
 * Returns proofs and the overrides required for the transaction.
 * @internal
 * @returns  `overrides` and `proofs` as an object.
 */
async function prepareClaim(addressToClaim, quantity, activeClaimCondition, merkleMetadataFetcher, tokenDecimals, contractWrapper, storage, checkERC20Allowance, snapshotFormatVersion) {
  let maxClaimable = convertQuantityToBigNumber(activeClaimCondition.maxClaimablePerWallet, tokenDecimals);
  let proofs = [utils.hexZeroPad([0], 32)];
  let priceInProof = activeClaimCondition.price; // the price to send to the contract in claim proofs
  let currencyAddressInProof = activeClaimCondition.currencyAddress;
  try {
    if (!activeClaimCondition.merkleRootHash.toString().startsWith(constants.AddressZero)) {
      const snapshotEntry = await fetchSnapshotEntryForAddress(addressToClaim, activeClaimCondition.merkleRootHash.toString(), await merkleMetadataFetcher(), contractWrapper.getProvider(), storage, snapshotFormatVersion);
      if (snapshotEntry) {
        proofs = snapshotEntry.proof;
        // override only if not default values (unlimited for quantity, zero addr for currency)
        maxClaimable = snapshotEntry.maxClaimable === "unlimited" ? constants.MaxUint256 : utils.parseUnits(snapshotEntry.maxClaimable, tokenDecimals);
        priceInProof = snapshotEntry.price === undefined || snapshotEntry.price === "unlimited" ? constants.MaxUint256 : await normalizePriceValue(contractWrapper.getProvider(), snapshotEntry.price, snapshotEntry.currencyAddress || constants.AddressZero);
        currencyAddressInProof = snapshotEntry.currencyAddress || constants.AddressZero;
      } else {
        // if no snapshot entry, and it's a v1 format (exclusive allowlist) then address can't claim
        if (snapshotFormatVersion === SnapshotFormatVersion.V1) {
          throw new Error("No claim found for this address");
        }
        // but if its snapshot v2 (override list behavior) then address can still claim with default settings
      }
    }
  } catch (e) {
    // have to handle the valid error case that we *do* want to throw on
    if (e?.message === "No claim found for this address") {
      throw e;
    }
    // other errors we wanna ignore and try to continue
    console.warn("failed to check claim condition merkle root hash, continuing anyways", e);
  }
  const overrides = (await contractWrapper.getCallOverrides()) || {};
  // the actual price to check allowance against
  // if proof price is unlimited, then we use the price from the claim condition
  // this mimics the contract behavior
  const pricePerToken = priceInProof.toString() !== constants.MaxUint256.toString() ? priceInProof : activeClaimCondition.price;
  // same for currency address
  const currencyAddress = currencyAddressInProof !== constants.AddressZero ? currencyAddressInProof : activeClaimCondition.currencyAddress;
  if (pricePerToken.gt(0)) {
    if (isNativeToken(currencyAddress)) {
      overrides["value"] = BigNumber.from(pricePerToken).mul(quantity).div(utils.parseUnits("1", tokenDecimals));
    } else if (checkERC20Allowance) {
      await approveErc20Allowance(contractWrapper, currencyAddress, pricePerToken, quantity, tokenDecimals);
    }
  }
  return {
    overrides,
    proofs,
    maxClaimable,
    price: pricePerToken,
    currencyAddress: currencyAddress,
    priceInProof,
    currencyAddressInProof
  };
}

/**
 * @internal
 */
const CurrencySchema = /* @__PURE__ */(() => z.object({
  name: z.string(),
  symbol: z.string(),
  decimals: z.number()
}))();

/**
 * @internal
 */
const CurrencyValueSchema = /* @__PURE__ */(() => CurrencySchema.extend({
  value: BigNumberSchema,
  displayValue: z.string()
}))();

/**
 * @internal
 */
const ClaimConditionMetadataSchema = /* @__PURE__ */(() => z.object({
  name: z.string().optional()
}).catchall(z.unknown()))();

/**
 * @internal
 */
const ClaimConditionInputSchema = /* @__PURE__ */(() => z.object({
  startTime: StartDateSchema,
  currencyAddress: z.string().default(NATIVE_TOKEN_ADDRESS),
  price: AmountSchema.default(0),
  maxClaimableSupply: QuantitySchema,
  maxClaimablePerWallet: QuantitySchema,
  waitInSeconds: BigNumberishSchema.default(0),
  merkleRootHash: BytesLikeSchema.default(utils.hexZeroPad([0], 32)),
  snapshot: z.optional(SnapshotInputSchema).nullable(),
  metadata: ClaimConditionMetadataSchema.optional()
}))();

/**
 * @internal
 */
const ClaimConditionInputArray = /* @__PURE__ */(() => z.array(ClaimConditionInputSchema))();

/**
 * @internal
 */
const PartialClaimConditionInputSchema = /* @__PURE__ */(() => ClaimConditionInputSchema.partial())();

/**
 * @internal
 */
const ClaimConditionOutputSchema = /* @__PURE__ */(() => ClaimConditionInputSchema.extend({
  availableSupply: QuantitySchema,
  currentMintSupply: QuantitySchema,
  currencyMetadata: CurrencyValueSchema.default({
    value: BigNumber.from("0"),
    displayValue: "0",
    symbol: "",
    decimals: 18,
    name: ""
  }),
  price: BigNumberSchema,
  waitInSeconds: BigNumberSchema,
  startTime: BigNumberSchema.transform(n => new Date(n.toNumber() * 1000)),
  snapshot: SnapshotInputSchema.optional().nullable()
}))();

/**
 * Create a snapshot (merkle tree) from a list of addresses and uploads it to IPFS
 * @param snapshotInput - the list of addresses to hash
 * @param tokenDecimals - the token decimals
 * @param provider - the provider to use
 * @param storage - the storage to upload to
 * @param snapshotFormatVersion - the snapshot format version
 * @returns The generated snapshot and URI
 * @internal
 */
async function createSnapshot(snapshotInput, tokenDecimals, provider, storage, snapshotFormatVersion) {
  const input = await parseSnapshotInputs(snapshotInput);
  const addresses = input.map(i => i.address);
  const hasDuplicates = new Set(addresses).size < addresses.length;
  if (hasDuplicates) {
    throw new DuplicateLeafsError();
  }
  const tree = await ShardedMerkleTree.buildAndUpload(input, tokenDecimals, provider, storage, snapshotFormatVersion);
  return {
    merkleRoot: tree.shardedMerkleInfo.merkleRoot,
    snapshotUri: tree.uri
  };
}

/**
 * Create a MerkleTree based on an allow list of addresses and maxClaimable
 * @param snapshotInput - the list of addresses and maxClaimable
 * @param tokenDecimals - optional decimals for the token to claim (default 18)
 * @param version - optional version of the snapshot format (default V1)
 * @returns The generated MerkleTree
 */
async function createMerkleTreeFromAllowList(snapshotInput) {
  let tokenDecimals = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 18;
  let version = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : SnapshotFormatVersion.V1;
  const input = await parseSnapshotInputs(snapshotInput);
  const addresses = input.map(i => i.address);
  const hasDuplicates = new Set(addresses).size < addresses.length;
  if (hasDuplicates) {
    throw new DuplicateLeafsError();
  }
  const leaves = input.map(i => {
    return hashAllowListEntry(i, tokenDecimals, version);
  });
  const tree = new MerkleTree(leaves, utils.keccak256, {
    sort: true
  });
  return tree;
}

/**
 * Get the proofs for a given entry of addresses
 * @param merkleTree - the merkle tree to get the proof from
 * @param snapshotEntry - the entry to get the proof for
 * @param tokenDecimals - optional decimals for the token to claim (default 18)
 * @param version - optional version of the snapshot format (default V1)
 * @returns
 */
async function getProofsForAllowListEntry(merkleTree, snapshotEntry) {
  let tokenDecimals = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 18;
  let version = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : SnapshotFormatVersion.V1;
  return merkleTree.getProof(hashAllowListEntry(snapshotEntry, tokenDecimals, version)).map(value => "0x" + value.data.toString("hex"));
}

/**
 * Hash an allow list entry for use in a MerkleTree
 * @param snapshotEntry - the entry to hash
 * @param tokenDecimals - optional decimals for the token to claim (default 18)
 * @param version - optional version of the snapshot format (default V1)
 * @returns
 */
function hashAllowListEntry(snapshotEntry) {
  let tokenDecimals = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 18;
  let version = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : SnapshotFormatVersion.V1;
  return ShardedMerkleTree.hashEntry(snapshotEntry, tokenDecimals, tokenDecimals, version);
}

function compare(a, b) {
  const left = BigNumber.from(a);
  const right = BigNumber.from(b);
  if (left.eq(right)) {
    return 0;
  } else if (left.gt(right)) {
    return 1;
  } else {
    return -1;
  }
}

/**
 * @internal
 * Decorates claim conditions with merkle roots from snapshots if present
 * @param claimConditionInputs - The claim conditions to process
 * @param tokenDecimals - The token decimals to use
 * @param provider - The provider to use
 * @param storage - The storage to use
 * @param snapshotFormatVersion - The snapshot format version to use
 */
async function processSnapshotData(claimConditionInputs, tokenDecimals, provider, storage, snapshotFormatVersion) {
  const snapshotInfos = [];
  const inputsWithSnapshots = await Promise.all(claimConditionInputs.map(async conditionInput => {
    // check snapshots and upload if provided
    if (conditionInput.snapshot && conditionInput.snapshot.length > 0) {
      const snapshotInfo = await createSnapshot(conditionInput.snapshot, tokenDecimals, provider, storage, snapshotFormatVersion);
      snapshotInfos.push(snapshotInfo);
      conditionInput.merkleRootHash = snapshotInfo.merkleRoot;
    } else {
      // if no snapshot is passed or empty, reset the merkle root
      conditionInput.merkleRootHash = utils.hexZeroPad([0], 32);
    }
    // fill condition with defaults values if not provided
    return conditionInput;
  }));
  return {
    inputsWithSnapshots,
    snapshotInfos
  };
}

/**
 * Converts a local SDK model to contract model
 * @param c - The condition input
 * @param tokenDecimals - The token decimals to use
 * @param provider - The provider to use
 * @param storage - The storage to use
 * @internal
 */
async function convertToContractModel(c, tokenDecimals, provider, storage) {
  const currency = c.currencyAddress === constants.AddressZero ? NATIVE_TOKEN_ADDRESS : c.currencyAddress;
  const maxClaimableSupply = convertQuantityToBigNumber(c.maxClaimableSupply, tokenDecimals);
  const maxClaimablePerWallet = convertQuantityToBigNumber(c.maxClaimablePerWallet, tokenDecimals);
  let metadataOrUri;
  if (c.metadata) {
    if (typeof c.metadata === "string") {
      metadataOrUri = c.metadata;
    } else {
      metadataOrUri = await storage.upload(c.metadata);
    }
  }
  return {
    startTimestamp: c.startTime,
    maxClaimableSupply,
    supplyClaimed: 0,
    maxClaimablePerWallet,
    pricePerToken: await normalizePriceValue(provider, c.price, currency),
    currency,
    merkleRoot: c.merkleRootHash.toString(),
    waitTimeInSecondsBetweenClaims: c.waitInSeconds || 0,
    metadata: metadataOrUri
  };
}

/**
 * Create and uploads snapshots + converts claim conditions to contract format
 * @param claimConditionInputs - The claim conditions to process
 * @param tokenDecimals - The token decimals to use
 * @param provider - The provider to use
 * @param storage - The storage to use
 * @param snapshotFormatVersion - The snapshot format version to use
 * @internal
 */
async function processClaimConditionInputs(claimConditionInputs, tokenDecimals, provider, storage, snapshotFormatVersion) {
  const {
    inputsWithSnapshots,
    snapshotInfos
  } = await processSnapshotData(claimConditionInputs, tokenDecimals, provider, storage, snapshotFormatVersion);
  const parsedInputs = await ClaimConditionInputArray.parseAsync(inputsWithSnapshots);
  // Convert processed inputs to the format the contract expects, and sort by timestamp
  const sortedConditions = (await Promise.all(parsedInputs.map(c => convertToContractModel(c, tokenDecimals, provider, storage)))).sort((a, b) => {
    return compare(a.startTimestamp, b.startTimestamp);
  });
  return {
    snapshotInfos,
    sortedConditions
  };
}

/**
 * @internal
 * @param merkleRoot - The merkle root to fetch the snapshot for
 * @param merkleMetadata - The merkle metadata to use
 * @param storage - The storage to use
 */
async function fetchSnapshot(merkleRoot, merkleMetadata, storage) {
  if (!merkleMetadata) {
    return null;
  }
  const snapshotUri = merkleMetadata[merkleRoot];
  if (snapshotUri) {
    const raw = await storage.downloadJSON(snapshotUri);
    if (raw.isShardedMerkleTree && raw.merkleRoot === merkleRoot) {
      const smt = await ShardedMerkleTree.fromUri(snapshotUri, storage);
      return smt?.getAllEntries() || null;
    } else {
      const snapshotData = await SnapshotSchema.parseAsync(raw);
      if (merkleRoot === snapshotData.merkleRoot) {
        return snapshotData.claims.map(claim => ({
          address: claim.address,
          maxClaimable: claim.maxClaimable,
          price: claim.price,
          currencyAddress: claim.currencyAddress
        }));
      }
    }
  }
  return null;
}

/**
 * @internal
 * @param bn - The big number to convert
 * @param tokenDecimals - The token decimals to use
 */
function convertToReadableQuantity(bn, tokenDecimals) {
  if (bn.toString() === constants.MaxUint256.toString()) {
    return "unlimited";
  } else {
    return utils.formatUnits(bn, tokenDecimals);
  }
}

/**
 * Transforms a contract model to local model
 * @param pm - The contract model to transform
 * @param tokenDecimals - The token decimals to use
 * @param provider - The provider to use
 * @param merkleMetadata - The merkle metadata to use
 * @param storage - The storage to use
 * @param shouldDownloadSnapshot - Whether to download the snapshot
 * @internal
 */
async function transformResultToClaimCondition(pm, tokenDecimals, provider, merkleMetadata, storage, shouldDownloadSnapshot) {
  const cv = await fetchCurrencyValue(provider, pm.currency, pm.pricePerToken);
  const maxClaimableSupply = convertToReadableQuantity(pm.maxClaimableSupply, tokenDecimals);
  const maxClaimablePerWallet = convertToReadableQuantity(pm.maxClaimablePerWallet, tokenDecimals);
  const availableSupply = convertToReadableQuantity(BigNumber.from(pm.maxClaimableSupply).sub(pm.supplyClaimed), tokenDecimals);
  const currentMintSupply = convertToReadableQuantity(pm.supplyClaimed, tokenDecimals);
  let resolvedMetadata;
  if (pm.metadata) {
    resolvedMetadata = await storage.downloadJSON(pm.metadata);
  }
  return ClaimConditionOutputSchema.parseAsync({
    startTime: pm.startTimestamp,
    maxClaimableSupply,
    maxClaimablePerWallet,
    currentMintSupply,
    availableSupply,
    waitInSeconds: pm.waitTimeInSecondsBetweenClaims?.toString(),
    price: BigNumber.from(pm.pricePerToken),
    currency: pm.currency,
    currencyAddress: pm.currency,
    currencyMetadata: cv,
    merkleRootHash: pm.merkleRoot,
    snapshot: shouldDownloadSnapshot ? await fetchSnapshot(pm.merkleRoot, merkleMetadata, storage) : undefined,
    metadata: resolvedMetadata
  });
}

/**
 * @internal
 * @param index - The index of the condition to update
 * @param claimConditionInput - The input claim condition to update
 * @param existingConditions - The existing claim conditions
 */
async function updateExistingClaimConditions(index, claimConditionInput, existingConditions) {
  if (index >= existingConditions.length) {
    throw Error(`Index out of bounds - got index: ${index} with ${existingConditions.length} conditions`);
  }
  // merge input with existing claim condition
  const priceDecimals = existingConditions[index].currencyMetadata.decimals;
  const priceInWei = existingConditions[index].price;
  const priceInTokens = utils.formatUnits(priceInWei, priceDecimals);

  // merge existing (output format) with incoming (input format)
  const newConditionParsed = await ClaimConditionInputSchema.parseAsync({
    ...existingConditions[index],
    price: priceInTokens,
    ...claimConditionInput
  });

  // convert to output claim condition
  const mergedConditionOutput = await ClaimConditionOutputSchema.parseAsync({
    ...newConditionParsed,
    price: priceInWei
  });
  return existingConditions.map((existingOutput, i) => {
    let newConditionAtIndex;
    if (i === index) {
      newConditionAtIndex = mergedConditionOutput;
    } else {
      newConditionAtIndex = existingOutput;
    }
    const formattedPrice = utils.formatUnits(newConditionAtIndex.price, priceDecimals);
    return {
      ...newConditionAtIndex,
      price: formattedPrice // manually transform back to input price type
    };
  });
}

let ClaimEligibility = /*#__PURE__*/function (ClaimEligibility) {
  ClaimEligibility["NotEnoughSupply"] = "There is not enough supply to claim.";
  ClaimEligibility["AddressNotAllowed"] = "This address is not on the allowlist.";
  ClaimEligibility["WaitBeforeNextClaimTransaction"] = "Not enough time since last claim transaction. Please wait.";
  ClaimEligibility["ClaimPhaseNotStarted"] = "Claim phase has not started yet.";
  ClaimEligibility["AlreadyClaimed"] = "You have already claimed the token.";
  ClaimEligibility["WrongPriceOrCurrency"] = "Incorrect price or currency.";
  ClaimEligibility["OverMaxClaimablePerWallet"] = "Cannot claim more than maximum allowed quantity.";
  ClaimEligibility["NotEnoughTokens"] = "There are not enough tokens in the wallet to pay for the claim.";
  ClaimEligibility["NoActiveClaimPhase"] = "There is no active claim phase at the moment. Please check back in later.";
  ClaimEligibility["NoClaimConditionSet"] = "There is no claim condition set.";
  ClaimEligibility["NoWallet"] = "No wallet connected.";
  ClaimEligibility["Unknown"] = "No claim conditions found.";
  return ClaimEligibility;
}({});

function resolveOrGenerateId(requestUId) {
  if (requestUId === undefined) {
    const buffer = Buffer.alloc(16);
    v4({}, buffer);
    return utils.hexlify(utils.toUtf8Bytes(buffer.toString("hex")));
  } else {
    return utils.hexlify(requestUId);
  }
}

/**
 * @internal
 */
const BaseSignaturePayloadInput = /* @__PURE__ */(() => z.object({
  to: AddressOrEnsSchema.refine(address => address.toLowerCase() !== constants.AddressZero, {
    message: "Cannot create payload to mint to zero address"
  }),
  price: AmountSchema.default(0),
  currencyAddress: AddressSchema.default(NATIVE_TOKEN_ADDRESS),
  mintStartTime: StartDateSchema,
  mintEndTime: EndDateSchema,
  uid: z.string().optional().transform(arg => resolveOrGenerateId(arg)),
  primarySaleRecipient: AddressOrEnsSchema.default(constants.AddressZero)
}))();

/**
 * @internal
 */
const Signature20PayloadInput = /* @__PURE__ */(() => BaseSignaturePayloadInput.extend({
  quantity: AmountSchema
}))();

/**
 * @internal
 */
const Signature20PayloadOutput = /* @__PURE__ */(() => Signature20PayloadInput.extend({
  mintStartTime: BigNumberSchema,
  mintEndTime: BigNumberSchema
}))();

/**
 * @internal
 */
const Signature721PayloadInput = /* @__PURE__ */(() => BaseSignaturePayloadInput.extend({
  metadata: NFTInputOrUriSchema,
  royaltyRecipient: z.string().default(constants.AddressZero),
  royaltyBps: BasisPointsSchema.default(0)
}))();

/**
 * @internal
 */
const Signature721PayloadOutput = /* @__PURE__ */(() => Signature721PayloadInput.extend({
  metadata: NFTInputOrUriSchema.default(""),
  uri: z.string(),
  royaltyBps: BigNumberSchema,
  mintStartTime: BigNumberSchema,
  mintEndTime: BigNumberSchema
}))();

/**
 * @internal
 */
const Signature1155PayloadInput = /* @__PURE__ */(() => Signature721PayloadInput.extend({
  metadata: NFTInputOrUriSchema.default(""),
  quantity: BigNumberishSchema
}))();

/**
 * @internal
 */
const Signature1155PayloadInputWithTokenId = /* @__PURE__ */(() => Signature1155PayloadInput.extend({
  tokenId: BigNumberishSchema
}))();

/**
 * @internal
 */
const Signature1155PayloadOutput = /* @__PURE__ */(() => Signature721PayloadOutput.extend({
  tokenId: BigNumberSchema,
  quantity: BigNumberSchema
}))();

/**
 * @internal
 */
const Signature721WithQuantityInput = /* @__PURE__ */(() => Signature721PayloadInput.extend({
  metadata: NFTInputOrUriSchema.default(""),
  quantity: BigNumberSchema.default(1)
}))();

/**
 * @internal
 */
const Signature721WithQuantityOutput = /* @__PURE__ */(() => Signature721PayloadOutput.extend({
  quantity: BigNumberSchema.default(1)
}))();

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

/**
 * @public
 */

const MintRequest20 = [{
  name: "to",
  type: "address"
}, {
  name: "primarySaleRecipient",
  type: "address"
}, {
  name: "quantity",
  type: "uint256"
}, {
  name: "price",
  type: "uint256"
}, {
  name: "currency",
  type: "address"
}, {
  name: "validityStartTimestamp",
  type: "uint128"
}, {
  name: "validityEndTimestamp",
  type: "uint128"
}, {
  name: "uid",
  type: "bytes32"
}];
const MintRequest721 = [{
  name: "to",
  type: "address"
}, {
  name: "royaltyRecipient",
  type: "address"
}, {
  name: "royaltyBps",
  type: "uint256"
}, {
  name: "primarySaleRecipient",
  type: "address"
}, {
  name: "uri",
  type: "string"
}, {
  name: "price",
  type: "uint256"
}, {
  name: "currency",
  type: "address"
}, {
  name: "validityStartTimestamp",
  type: "uint128"
}, {
  name: "validityEndTimestamp",
  type: "uint128"
}, {
  name: "uid",
  type: "bytes32"
}];
const MintRequest1155 = [{
  name: "to",
  type: "address"
}, {
  name: "royaltyRecipient",
  type: "address"
}, {
  name: "royaltyBps",
  type: "uint256"
}, {
  name: "primarySaleRecipient",
  type: "address"
}, {
  name: "tokenId",
  type: "uint256"
}, {
  name: "uri",
  type: "string"
}, {
  name: "quantity",
  type: "uint256"
}, {
  name: "pricePerToken",
  type: "uint256"
}, {
  name: "currency",
  type: "address"
}, {
  name: "validityStartTimestamp",
  type: "uint128"
}, {
  name: "validityEndTimestamp",
  type: "uint128"
}, {
  name: "uid",
  type: "bytes32"
}];
const MintRequest721withQuantity = [{
  name: "to",
  type: "address"
}, {
  name: "royaltyRecipient",
  type: "address"
}, {
  name: "royaltyBps",
  type: "uint256"
}, {
  name: "primarySaleRecipient",
  type: "address"
}, {
  name: "uri",
  type: "string"
}, {
  name: "quantity",
  type: "uint256"
}, {
  name: "pricePerToken",
  type: "uint256"
}, {
  name: "currency",
  type: "address"
}, {
  name: "validityStartTimestamp",
  type: "uint128"
}, {
  name: "validityEndTimestamp",
  type: "uint128"
}, {
  name: "uid",
  type: "bytes32"
}];
const GenericRequest = [{
  name: "validityStartTimestamp",
  type: "uint128"
}, {
  name: "validityEndTimestamp",
  type: "uint128"
}, {
  name: "uid",
  type: "bytes32"
}, {
  name: "data",
  type: "bytes"
}];

export { newContractModelToAbstract as A, BaseSignaturePayloadInput as B, ClaimEligibility as C, convertQuantityToBigNumber as D, prepareClaim as E, SnapshotFormatVersion as F, GenericRequest as G, processClaimConditionInputs as H, abstractContractModelToLegacy as I, abstractContractModelToNew as J, updateExistingClaimConditions as K, resolveOrGenerateId as L, MintRequest20 as M, PartialClaimConditionInputSchema as P, Signature20PayloadInput as S, approveErc20Allowance as a, createSnapshot as b, convertToReadableQuantity as c, createMerkleTreeFromAllowList as d, ClaimConditionMetadataSchema as e, fetchSnapshotEntryForAddress as f, getProofsForAllowListEntry as g, hashAllowListEntry as h, ClaimConditionInputSchema as i, ClaimConditionInputArray as j, ClaimConditionOutputSchema as k, CurrencySchema as l, CurrencyValueSchema as m, Signature20PayloadOutput as n, Signature721PayloadInput as o, Signature721PayloadOutput as p, Signature1155PayloadInput as q, Signature1155PayloadInputWithTokenId as r, Signature1155PayloadOutput as s, Signature721WithQuantityInput as t, Signature721WithQuantityOutput as u, MintRequest721 as v, MintRequest1155 as w, MintRequest721withQuantity as x, transformResultToClaimCondition as y, legacyContractModelToAbstract as z };
