/* eslint-disable eslint-comments/disable-enable-pair */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable no-nested-ternary */

/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import { toUtf8 } from '@cosmjs/encoding'
import { coin } from '@cosmjs/proto-signing'
import axios from 'axios'
import clsx from 'clsx'
import { Button } from 'components/Button'
import type { MinterType } from 'components/collections/actions/Combobox'
import { Conditional } from 'components/Conditional'
import { ConfirmationModal } from 'components/ConfirmationModal'
import { LoadingModal } from 'components/LoadingModal'
import { type TokenInfo } from 'config/token'
import { useContracts } from 'contexts/contracts'
import { addLogItem } from 'contexts/log'
import type { DispatchExecuteArgs as OpenEditionFactoryDispatchExecuteArgs } from 'contracts/openEditionFactory/messages/execute'
import { dispatchExecute as openEditionFactoryDispatchExecute } from 'contracts/openEditionFactory/messages/execute'
import React, { useEffect, useMemo, useState } from 'react'
import { toast } from 'react-hot-toast'
import { upload } from 'services/upload'
import {
  SG721_OPEN_EDITION_CODE_ID,
  SG721_OPEN_EDITION_UPDATABLE_CODE_ID,
  STRDST_SG721_CODE_ID,
  WHITELIST_CODE_ID,
  WHITELIST_FLEX_CODE_ID,
  WHITELIST_MERKLE_TREE_API_URL,
  WHITELIST_MERKLE_TREE_CODE_ID,
} from 'utils/constants'
import { useDebounce } from 'utils/debounce'
import type { AssetType } from 'utils/getAssetType'
import { isValidAddress } from 'utils/isValidAddress'
import { checkTokenUri } from 'utils/isValidTokenUri'
import { uid } from 'utils/random'
import { useWallet } from 'utils/wallet'

import { type CollectionDetailsDataProps, CollectionDetails } from './CollectionDetails'
import type { ImageUploadDetailsDataProps } from './ImageUploadDetails'
import { ImageUploadDetails } from './ImageUploadDetails'
import type { LimitType, MintingDetailsDataProps } from './MintingDetails'
import { MintingDetails } from './MintingDetails'
import type { UploadMethod } from './OffChainMetadataUploadDetails'
import {
  type OffChainMetadataUploadDetailsDataProps,
  OffChainMetadataUploadDetails,
} from './OffChainMetadataUploadDetails'
import type { OnChainMetadataInputDetailsDataProps } from './OnChainMetadataInputDetails'
import { OnChainMetadataInputDetails } from './OnChainMetadataInputDetails'
import { type RoyaltyDetailsDataProps, RoyaltyDetails } from './RoyaltyDetails'
import { type WhitelistDetailsDataProps, WhitelistDetails } from './WhitelistDetails'

export type MetadataStorageMethod = 'off-chain' | 'on-chain'

export interface OpenEditionMinterDetailsDataProps {
  imageUploadDetails?: ImageUploadDetailsDataProps
  collectionDetails?: CollectionDetailsDataProps
  whitelistDetails?: WhitelistDetailsDataProps
  royaltyDetails?: RoyaltyDetailsDataProps
  onChainMetadataInputDetails?: OnChainMetadataInputDetailsDataProps
  offChainMetadataUploadDetails?: OffChainMetadataUploadDetailsDataProps
  mintingDetails?: MintingDetailsDataProps
  metadataStorageMethod?: MetadataStorageMethod
  openEditionMinterContractAddress?: string | null
  coverImageUrl?: string | null
  tokenUri?: string | null
  tokenImageUri?: string | null
  isRefreshed?: boolean
}

interface OpenEditionMinterCreatorProps {
  onChange: (data: OpenEditionMinterCreatorDataProps) => void
  onDetailsChange: (data: OpenEditionMinterDetailsDataProps) => void
  openEditionMinterCreationFee?: string
  minimumMintPrice?: string
  minterType?: MinterType
  mintTokenFromFactory?: TokenInfo | undefined
  importedOpenEditionMinterDetails?: OpenEditionMinterDetailsDataProps
  isMatchingFactoryPresent?: boolean
  openEditionFactoryAddress?: string
}

export interface OpenEditionMinterCreatorDataProps {
  metadataStorageMethod: MetadataStorageMethod
  openEditionMinterContractAddress: string | null
  sg721ContractAddress: string | null
  whitelistContractAddress: string | null
  transactionHash: string | null
}

export const OpenEditionMinterCreator = ({
  onChange,
  onDetailsChange,
  openEditionMinterCreationFee,
  minimumMintPrice,
  minterType,
  mintTokenFromFactory,
  importedOpenEditionMinterDetails,
  isMatchingFactoryPresent,
  openEditionFactoryAddress,
}: OpenEditionMinterCreatorProps) => {
  const wallet = useWallet()
  const {
    openEditionMinter: openEditionMinterContract,
    openEditionFactory: openEditionFactoryContract,
    whitelist: whitelistContract,
    whitelistMerkleTree: whitelistMerkleTreeContract,
  } = useContracts()

  const [metadataStorageMethod, setMetadataStorageMethod] = useState<MetadataStorageMethod>('off-chain')
  const [imageUploadDetails, setImageUploadDetails] = useState<ImageUploadDetailsDataProps | null>(null)
  const [collectionDetails, setCollectionDetails] = useState<CollectionDetailsDataProps | null>(null)
  const [whitelistDetails, setWhitelistDetails] = useState<WhitelistDetailsDataProps | null>(null)
  const [royaltyDetails, setRoyaltyDetails] = useState<RoyaltyDetailsDataProps | null>(null)
  const [isRefreshed, setIsRefreshed] = useState(false)
  const [onChainMetadataInputDetails, setOnChainMetadataInputDetails] =
    useState<OnChainMetadataInputDetailsDataProps | null>(null)
  const [offChainMetadataUploadDetails, setOffChainMetadataUploadDetails] =
    useState<OffChainMetadataUploadDetailsDataProps | null>(null)
  const [mintingDetails, setMintingDetails] = useState<MintingDetailsDataProps | null>(null)

  const [creationInProgress, setCreationInProgress] = useState(false)
  const [readyToCreate, setReadyToCreate] = useState(false)
  const [uploading, setUploading] = useState(false)
  const [tokenUri, setTokenUri] = useState<string | null>(null)
  const [tokenImageUri, setTokenImageUri] = useState<string | null>(null)
  const [coverImageUrl, setCoverImageUrl] = useState<string | null>(null)
  const [openEditionMinterContractAddress, setOpenEditionMinterContractAddress] = useState<string | null>(null)
  const [sg721ContractAddress, setSg721ContractAddress] = useState<string | null>(null)
  const [whitelistContractAddress, setWhitelistContractAddress] = useState<string | null>(null)
  const [transactionHash, setTransactionHash] = useState<string | null>(null)
  const [thumbnailImageUri, setThumbnailImageUri] = useState<string | undefined>(undefined)

  const thumbnailCompatibleAssetTypes: AssetType[] = ['video', 'audio', 'html']

  const openEditionFactoryMessages = useMemo(
    () => openEditionFactoryContract?.use(openEditionFactoryAddress as string),
    [
      openEditionFactoryContract,
      wallet.address,
      collectionDetails?.updatable,
      openEditionFactoryAddress,
      wallet.isWalletConnected,
    ],
  )

  const performOpenEditionMinterChecks = () => {
    try {
      setReadyToCreate(false)
      checkCollectionDetails()
      checkMintingDetails()
      void checkUploadDetails()
        .then(() => {
          void checkRoyaltyDetails()
            .then(() => {
              checkWhitelistDetails()
                .then(() => {
                  void checkwalletBalance()
                    .then(() => {
                      setReadyToCreate(true)
                    })
                    .catch((error: any) => {
                      toast.error(`Error in Wallet Balance: ${error.message}`, { style: { maxWidth: 'none' } })
                      addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
                      setReadyToCreate(false)
                    })
                })
                .catch((error) => {
                  if (String(error.message).includes('Insufficient wallet balance')) {
                    toast.error(`${error.message}`, { style: { maxWidth: 'none' } })
                    addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
                  } else {
                    toast.error(`Error in Whitelist Configuration: ${error.message}`, { style: { maxWidth: 'none' } })
                    addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
                  }
                  setReadyToCreate(false)
                })
            })
            .catch((error: any) => {
              toast.error(`Error in Royalty Details: ${error.message}`, { style: { maxWidth: 'none' } })
              addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
              setReadyToCreate(false)
            })
        })
        .catch((error: any) => {
          toast.error(`Error in Upload Details: ${error.message}`, { style: { maxWidth: 'none' } })
          addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
          setReadyToCreate(false)
        })
    } catch (error: any) {
      toast.error(error.message, { style: { maxWidth: 'none' } })
      addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
      setUploading(false)
      setReadyToCreate(false)
    }
  }

  const checkUploadDetails = async () => {
    if (!wallet.isWalletConnected) throw new Error('Wallet not connected.')
    if (
      (metadataStorageMethod === 'off-chain' && !offChainMetadataUploadDetails) ||
      (metadataStorageMethod === 'on-chain' && !imageUploadDetails)
    ) {
      throw new Error('Please select assets and metadata')
    }

    if (
      metadataStorageMethod === 'off-chain' &&
      offChainMetadataUploadDetails?.uploadMethod === 'new' &&
      offChainMetadataUploadDetails.assetFiles.length === 0
    ) {
      throw new Error('Please select the asset file')
    }
    if (
      metadataStorageMethod === 'on-chain' &&
      imageUploadDetails?.uploadMethod === 'new' &&
      imageUploadDetails.assetFile === undefined
    ) {
      throw new Error('Please select the asset file')
    }
    if (metadataStorageMethod === 'off-chain' && offChainMetadataUploadDetails?.uploadMethod === 'new') {
      if (
        offChainMetadataUploadDetails.uploadService === 'nft-storage' &&
        offChainMetadataUploadDetails.nftStorageApiKey === ''
      ) {
        throw new Error('Please enter a valid NFT.Storage API key')
      } else if (
        offChainMetadataUploadDetails.uploadService === 'pinata' &&
        (offChainMetadataUploadDetails.pinataApiKey === '' || offChainMetadataUploadDetails.pinataSecretKey === '')
      ) {
        throw new Error('Please enter valid Pinata API and secret keys')
      }
    }
    if (metadataStorageMethod === 'on-chain' && imageUploadDetails?.uploadMethod === 'new') {
      if (imageUploadDetails.uploadService === 'nft-storage' && imageUploadDetails.nftStorageApiKey === '') {
        throw new Error('Please enter a valid NFT.Storage API key')
      } else if (
        imageUploadDetails.uploadService === 'pinata' &&
        (imageUploadDetails.pinataApiKey === '' || imageUploadDetails.pinataSecretKey === '')
      ) {
        throw new Error('Please enter valid Pinata API and secret keys')
      }
    }
    if (metadataStorageMethod === 'off-chain' && offChainMetadataUploadDetails?.uploadMethod === 'existing') {
      if (
        offChainMetadataUploadDetails.tokenURI === '' ||
        !(offChainMetadataUploadDetails.tokenURI as string).includes('ipfs://')
      ) {
        throw new Error('Please enter a valid token URI')
      }
      if (
        offChainMetadataUploadDetails.imageUrl === '' ||
        !(offChainMetadataUploadDetails.imageUrl as string).includes('ipfs://')
      ) {
        throw new Error('Please enter a valid image URI')
      }
    }
    if (metadataStorageMethod === 'on-chain' && imageUploadDetails?.uploadMethod === 'existing') {
      if (imageUploadDetails.imageUrl === '' || !(imageUploadDetails.imageUrl as string).includes('ipfs://')) {
        throw new Error('Please enter a valid asset URI')
      }
      if (
        imageUploadDetails.coverImageUrl === '' ||
        !(imageUploadDetails.coverImageUrl as string).includes('ipfs://')
      ) {
        throw new Error('Please enter a valid cover image URL')
      }
    }
    if (offChainMetadataUploadDetails?.uploadMethod === 'existing') {
      await checkTokenUri(offChainMetadataUploadDetails.tokenURI as string)
    }
  }

  const checkCollectionDetails = () => {
    if (!collectionDetails) throw new Error('Please fill out the collection details')
    if (collectionDetails.name === '') throw new Error('Collection name is required')
    if (collectionDetails.description === '') throw new Error('Collection description is required')
    if (collectionDetails.symbol === '') throw new Error('Collection symbol is required')
    if (collectionDetails.description.length > 512)
      throw new Error('Collection description cannot exceed 512 characters')
    if (
      metadataStorageMethod === 'off-chain' &&
      offChainMetadataUploadDetails?.uploadMethod === 'new' &&
      collectionDetails.imageFile.length === 0
    )
      throw new Error('Collection cover image is required')
    if (
      metadataStorageMethod === 'on-chain' &&
      imageUploadDetails?.uploadMethod === 'new' &&
      collectionDetails.imageFile.length === 0
    )
      throw new Error('Collection cover image is required')
    if (
      collectionDetails.startTradingTime &&
      Number(collectionDetails.startTradingTime) < new Date().getTime() * 1000000
    )
      throw new Error('Invalid trading start time')
    if (
      collectionDetails.startTradingTime &&
      Number(collectionDetails.startTradingTime) < Number(mintingDetails?.startTime)
    )
      throw new Error('Trading start time must be after minting start time')
    if (collectionDetails.externalLink) {
      try {
        const url = new URL(collectionDetails.externalLink)
      } catch (e: any) {
        throw new Error(`Invalid external link: Make sure to include the protocol (e.g. https://)`)
      }
    }
  }

  const checkMintingDetails = () => {
    if (!mintingDetails) throw new Error('Please fill out the minting details')
    if (mintingDetails.unitPrice === '') throw new Error('Mint price is required')
    if (collectionDetails?.updatable) {
      if (Number(mintingDetails.unitPrice) < Number(minimumMintPrice))
        throw new Error(
          `Invalid mint price: The minimum mint price is ${Number(minimumMintPrice) / 1000000} ${
            mintTokenFromFactory?.displayName
          }`,
        )
    } else if (Number(mintingDetails.unitPrice) < Number(minimumMintPrice))
      throw new Error(
        `Invalid mint price: The minimum mint price is ${Number(minimumMintPrice) / 1000000} ${
          mintTokenFromFactory?.displayName
        }`,
      )
    if (!mintingDetails.perAddressLimit || mintingDetails.perAddressLimit < 1 || mintingDetails.perAddressLimit > 50)
      throw new Error('Invalid limit for tokens per address')
    if (mintingDetails.startTime === '') throw new Error('Start time is required')
    if (mintingDetails.limitType === 'time_limited' && mintingDetails.endTime === '')
      throw new Error('End time is required')
    if (mintingDetails.limitType === 'count_limited' && mintingDetails.tokenCountLimit === undefined)
      throw new Error('Token count limit is required')
    if (
      mintingDetails.limitType === 'count_limited' &&
      mintingDetails.perAddressLimit > (mintingDetails.tokenCountLimit as number)
    )
      throw new Error('Per address limit cannot exceed maximum token count limit')
    if (mintingDetails.limitType === 'count_limited' && (mintingDetails.tokenCountLimit as number) > 10000)
      throw new Error('Maximum token count cannot exceed 10000')
    if (Number(mintingDetails.startTime) < new Date().getTime() * 1000000) throw new Error('Invalid start time')
    if (
      mintingDetails.limitType === 'time_limited' &&
      Number(mintingDetails.endTime) < Number(mintingDetails.startTime)
    )
      throw new Error('End time cannot be earlier than start time')
    if (
      mintingDetails.limitType === 'time_limited' &&
      Number(mintingDetails.endTime) === Number(mintingDetails.startTime)
    )
      throw new Error('End time cannot be equal to the start time')

    if (
      mintingDetails.paymentAddress &&
      (!isValidAddress(mintingDetails.paymentAddress) || !mintingDetails.paymentAddress.startsWith('stars1'))
    )
      throw new Error('Invalid payment address')

    if (!isMatchingFactoryPresent)
      throw new Error(
        `No matching open edition factory contract found for the selected parameters (Mint Price Denom: ${mintingDetails.selectedMintToken?.displayName}, Whitelist Type: ${whitelistDetails?.whitelistType})`,
      )
  }

  const checkWhitelistDetails = async () => {
    if (!whitelistDetails) throw new Error('Please fill out the whitelist details')
    if (whitelistDetails.whitelistState === 'existing') {
      if (whitelistDetails.contractAddress === '') throw new Error('Whitelist contract address is required')
      else {
        const contract = whitelistContract?.use(whitelistDetails.contractAddress)
        //check if the address belongs to a whitelist contract (see performChecks())
        const config = await contract?.config()
        if (JSON.stringify(config).includes('whale_cap')) whitelistDetails.whitelistType = 'flex'
        else if (!JSON.stringify(config).includes('member_limit') || config?.member_limit === 0) {
          // whitelistDetails.whitelistType = 'merkletree'
          throw new Error(
            'Whitelist Merkle Tree is not supported yet. Please use a standard or flexible whitelist contract.',
          )
        } else whitelistDetails.whitelistType = 'standard'
        if (Number(config?.start_time) !== Number(mintingDetails?.startTime)) {
          const whitelistStartDate = new Date(Number(config?.start_time) / 1000000)
          throw Error(`Whitelist start time (${whitelistStartDate.toLocaleString()}) does not match minting start time`)
        }

        if (mintingDetails?.tokenCountLimit && config?.per_address_limit) {
          if (mintingDetails.tokenCountLimit >= 100 && Number(config.per_address_limit) > 50) {
            throw Error(
              `Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 50 regardless of the total number of tokens.`,
            )
          } else if (
            mintingDetails.tokenCountLimit >= 100 &&
            Number(config.per_address_limit) > Math.ceil((mintingDetails.tokenCountLimit / 100) * 3)
          ) {
            throw Error(
              `Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 3% of the total number of tokens in the collection.`,
            )
          } else if (mintingDetails.tokenCountLimit < 100 && Number(config.per_address_limit) > 3) {
            throw Error(
              `Invalid limit for tokens per address (${config.per_address_limit} tokens). Tokens per address limit cannot exceed 3 for collections with a token count limit smaller than 100 tokens.`,
            )
          }
        }
      }
    } else if (whitelistDetails.whitelistState === 'new') {
      if (whitelistDetails.members?.length === 0) throw new Error('Whitelist member list cannot be empty')
      if (whitelistDetails.unitPrice === undefined) throw new Error('Whitelist unit price is required')
      if (Number(whitelistDetails.unitPrice) < 0)
        throw new Error('Invalid unit price: The unit price cannot be negative')
      if (whitelistDetails.startTime === '') throw new Error('Start time is required')
      if (whitelistDetails.endTime === '') throw new Error('End time is required')
      if (
        whitelistDetails.whitelistType === 'standard' &&
        (!whitelistDetails.perAddressLimit || whitelistDetails.perAddressLimit === 0)
      )
        throw new Error('Per address limit is required')
      if (
        whitelistDetails.whitelistType !== 'merkletree' &&
        (!whitelistDetails.memberLimit || whitelistDetails.memberLimit === 0)
      )
        throw new Error('Member limit is required')
      if (Number(whitelistDetails.startTime) >= Number(whitelistDetails.endTime))
        throw new Error('Whitelist start time cannot be equal to or later than the whitelist end time')
      if (Number(whitelistDetails.startTime) !== Number(mintingDetails?.startTime))
        throw new Error('Whitelist start time must be the same as the minting start time')
      if (whitelistDetails.perAddressLimit && mintingDetails?.tokenCountLimit) {
        if (mintingDetails.tokenCountLimit >= 100 && whitelistDetails.perAddressLimit > 50) {
          throw Error(
            `Invalid limit for tokens per address. Tokens per address limit cannot exceed 50 regardless of the total number of tokens.`,
          )
        } else if (
          mintingDetails.tokenCountLimit >= 100 &&
          whitelistDetails.perAddressLimit > Math.ceil((mintingDetails.tokenCountLimit / 100) * 3)
        ) {
          throw Error(
            `Invalid limit for tokens per address. Tokens per address limit cannot exceed 3% of the total number of tokens in the collection.`,
          )
        } else if (mintingDetails.tokenCountLimit < 100 && whitelistDetails.perAddressLimit > 3) {
          throw Error(
            `Invalid limit for tokens per address. Tokens per address limit cannot exceed 3 for collections with a token count limit smaller than 100 tokens.`,
          )
        }
      }
    }
  }

  const checkRoyaltyDetails = async () => {
    if (!royaltyDetails) throw new Error('Please fill out the royalty details')
    if (royaltyDetails.royaltyType === 'new') {
      if (royaltyDetails.share === 0) throw new Error('Royalty share percentage is required')
      if (royaltyDetails.share > 100 || royaltyDetails.share < 0) throw new Error('Invalid royalty share percentage')
      if (royaltyDetails.paymentAddress === '') throw new Error('Royalty payment address is required')
      if (!isValidAddress(royaltyDetails.paymentAddress.trim())) {
        if (royaltyDetails.paymentAddress.trim().endsWith('.stars')) {
          throw new Error('Royalty payment address could not be resolved')
        }
        throw new Error('Invalid royalty payment address')
      }
      const contractInfoResponse = await (await wallet.getCosmWasmClient())
        .queryContractRaw(
          royaltyDetails.paymentAddress.trim(),
          toUtf8(Buffer.from(Buffer.from('contract_info').toString('hex'), 'hex').toString()),
        )
        .catch((e) => {
          // eslint-disable-next-line @typescript-eslint/no-unsafe-call
          if (e.message.includes('bech32')) throw new Error('Invalid royalty payment address.')
          console.log(e.message)
        })
      if (contractInfoResponse !== undefined) {
        const contractInfo = JSON.parse(new TextDecoder().decode(contractInfoResponse as Uint8Array))
        // eslint-disable-next-line @typescript-eslint/no-unsafe-call
        if (contractInfo && (contractInfo.contract.includes('minter') || contractInfo.contract.includes('sg721')))
          throw new Error('The provided royalty payment address does not belong to a compatible contract.')
        else console.log(contractInfo)
      }
    }
  }

  const checkwalletBalance = async () => {
    if (!wallet.isWalletConnected) throw new Error('Wallet not connected.')
    let amountNeeded = 0
    if (whitelistDetails?.whitelistState === 'new' && whitelistDetails.memberLimit) {
      amountNeeded =
        Math.ceil(Number(whitelistDetails.memberLimit) / 1000) * 100000000 + Number(openEditionMinterCreationFee)
    } else {
      amountNeeded = openEditionMinterCreationFee ? Number(openEditionMinterCreationFee) : 0
    }
    await (await wallet.getCosmWasmClient()).getBalance(wallet.address || '', 'ustars').then((balance) => {
      if (amountNeeded >= Number(balance.amount))
        throw new Error(
          `Insufficient wallet balance to instantiate the required contracts. Needed amount: ${(
            amountNeeded / 1000000
          ).toString()} STARS`,
        )
    })
  }

  const createOpenEditionMinter = async () => {
    try {
      setCreationInProgress(true)
      setTokenUri(null)
      setCoverImageUrl(null)
      setTokenImageUri(null)
      setOpenEditionMinterContractAddress(null)
      setSg721ContractAddress(null)
      setWhitelistContractAddress(null)
      setTransactionHash(null)
      if (metadataStorageMethod === 'off-chain') {
        if (offChainMetadataUploadDetails?.uploadMethod === 'new') {
          setUploading(true)
          const metadataUri = await uploadForOffChainStorage()
          const coverImageUri = await upload(
            collectionDetails?.imageFile as File[],
            offChainMetadataUploadDetails.uploadService,
            'cover',
            offChainMetadataUploadDetails.nftStorageApiKey as string,
            offChainMetadataUploadDetails.pinataApiKey as string,
            offChainMetadataUploadDetails.pinataSecretKey as string,
          )
          const metadataUriWithBase = `ipfs://${metadataUri}/${(
            offChainMetadataUploadDetails.openEditionMinterMetadataFile as File
          ).name.substring(
            0,
            (offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name.lastIndexOf('.'),
          )}`
          const coverImageUriWithBase = `ipfs://${coverImageUri}/${(collectionDetails?.imageFile as File[])[0].name}`

          setTokenUri(metadataUriWithBase)
          setCoverImageUrl(coverImageUriWithBase)
          setUploading(false)

          let whitelist: string | undefined
          if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
          else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
          setWhitelistContractAddress(whitelist as string)

          await instantiateOpenEditionMinter(metadataUriWithBase, coverImageUriWithBase, undefined, whitelist)
        } else {
          setTokenUri(offChainMetadataUploadDetails?.tokenURI as string)
          setCoverImageUrl(offChainMetadataUploadDetails?.imageUrl as string)

          let whitelist: string | undefined
          if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
          else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
          setWhitelistContractAddress(whitelist as string)

          await instantiateOpenEditionMinter(
            offChainMetadataUploadDetails?.tokenURI as string,
            offChainMetadataUploadDetails?.imageUrl as string,
            undefined,
            whitelist,
          )
        }
      } else if (metadataStorageMethod === 'on-chain') {
        if (imageUploadDetails?.uploadMethod === 'new') {
          setUploading(true)
          const imageUri = await upload(
            [imageUploadDetails.assetFile as File],
            imageUploadDetails.uploadService,
            'cover',
            imageUploadDetails.nftStorageApiKey as string,
            imageUploadDetails.pinataApiKey as string,
            imageUploadDetails.pinataSecretKey as string,
          )
          const imageUriWithBase = `ipfs://${imageUri}/${(imageUploadDetails.assetFile as File).name}`
          setTokenImageUri(imageUriWithBase)

          const coverImageUri = await upload(
            collectionDetails?.imageFile as File[],
            imageUploadDetails.uploadService,
            'cover',
            imageUploadDetails.nftStorageApiKey as string,
            imageUploadDetails.pinataApiKey as string,
            imageUploadDetails.pinataSecretKey as string,
          )
          const coverImageUriWithBase = `ipfs://${coverImageUri}/${(collectionDetails?.imageFile as File[])[0].name}`
          setCoverImageUrl(coverImageUriWithBase)

          let thumbnailUri: string | undefined
          if (imageUploadDetails.isThumbnailCompatible && imageUploadDetails.thumbnailFile)
            thumbnailUri = await upload(
              [imageUploadDetails.thumbnailFile] as File[],
              imageUploadDetails.uploadService,
              'thumbnail',
              imageUploadDetails.nftStorageApiKey as string,
              imageUploadDetails.pinataApiKey as string,
              imageUploadDetails.pinataSecretKey as string,
            )
          const thumbnailUriWithBase = thumbnailUri
            ? `ipfs://${thumbnailUri}/${(imageUploadDetails.thumbnailFile as File).name}`
            : undefined
          setThumbnailImageUri(thumbnailUriWithBase)
          setUploading(false)

          let whitelist: string | undefined
          if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
          else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
          setWhitelistContractAddress(whitelist as string)

          await instantiateOpenEditionMinter(imageUriWithBase, coverImageUriWithBase, thumbnailUriWithBase, whitelist)
        } else if (imageUploadDetails?.uploadMethod === 'existing') {
          setTokenImageUri(imageUploadDetails.imageUrl as string)
          setCoverImageUrl(imageUploadDetails.coverImageUrl as string)

          let whitelist: string | undefined
          if (whitelistDetails?.whitelistState === 'existing') whitelist = whitelistDetails.contractAddress
          else if (whitelistDetails?.whitelistState === 'new') whitelist = await instantiateWhitelist()
          setWhitelistContractAddress(whitelist as string)

          await instantiateOpenEditionMinter(
            imageUploadDetails.imageUrl as string,
            imageUploadDetails.coverImageUrl as string,
            whitelist,
          )
        }
      }
      setCreationInProgress(false)
      setReadyToCreate(false)
    } catch (error: any) {
      toast.error(error.message, { style: { maxWidth: 'none' }, duration: 10000 })
      addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
      setReadyToCreate(false)
      setCreationInProgress(false)
      setUploading(false)
    }
  }

  const uploadForOffChainStorage = async (): Promise<string> => {
    if (!offChainMetadataUploadDetails) throw new Error('Please select the asset and fill in the metadata')
    return new Promise((resolve, reject) => {
      upload(
        offChainMetadataUploadDetails.assetFiles,
        offChainMetadataUploadDetails.uploadService,
        'assets',
        offChainMetadataUploadDetails.nftStorageApiKey as string,
        offChainMetadataUploadDetails.pinataApiKey as string,
        offChainMetadataUploadDetails.pinataSecretKey as string,
      )
        .then(async (assetUri: string) => {
          let thumbnailUri: string | undefined
          if (offChainMetadataUploadDetails.isThumbnailCompatible && offChainMetadataUploadDetails.thumbnailFile)
            thumbnailUri = await upload(
              [offChainMetadataUploadDetails.thumbnailFile] as File[],
              offChainMetadataUploadDetails.uploadService,
              'thumbnail',
              offChainMetadataUploadDetails.nftStorageApiKey as string,
              offChainMetadataUploadDetails.pinataApiKey as string,
              offChainMetadataUploadDetails.pinataSecretKey as string,
            )
          const thumbnailUriWithBase = thumbnailUri
            ? `ipfs://${thumbnailUri}/${(offChainMetadataUploadDetails.thumbnailFile as File).name}`
            : undefined

          const fileArray: File[] = []
          const reader: FileReader = new FileReader()

          reader.onload = (e) => {
            const data: any = JSON.parse(e.target?.result as string)

            if (offChainMetadataUploadDetails.isThumbnailCompatible) {
              data.animation_url = `ipfs://${assetUri}/${offChainMetadataUploadDetails.assetFiles[0].name}`
            }

            data.image =
              offChainMetadataUploadDetails.isThumbnailCompatible && offChainMetadataUploadDetails.thumbnailFile
                ? thumbnailUriWithBase
                : `ipfs://${assetUri}/${offChainMetadataUploadDetails.assetFiles[0].name}`

            if (data.description) {
              // eslint-disable-next-line @typescript-eslint/no-unsafe-call
              data.description = data.description.replaceAll('\\n', '\n')
            }
            const metadataFileBlob = new Blob([JSON.stringify(data)], {
              type: 'application/json',
            })

            console.log('Name: ', (offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name)
            const updatedMetadataFile = new File(
              [metadataFileBlob],
              (offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name
                .substring(
                  0,
                  (offChainMetadataUploadDetails.openEditionMinterMetadataFile as File).name.lastIndexOf('.'),
                )
                .replaceAll('#', ''),
              {
                type: 'application/json',
              },
            )

            fileArray.push(updatedMetadataFile)
          }
          reader.onloadend = () => {
            upload(
              fileArray,
              offChainMetadataUploadDetails.uploadService,
              'metadata',
              offChainMetadataUploadDetails.nftStorageApiKey as string,
              offChainMetadataUploadDetails.pinataApiKey as string,
              offChainMetadataUploadDetails.pinataSecretKey as string,
            )
              .then(resolve)
              .catch(reject)
          }
          console.log('File: ', offChainMetadataUploadDetails.openEditionMinterMetadataFile)
          reader.readAsText(offChainMetadataUploadDetails.openEditionMinterMetadataFile as File, 'utf8')
        })
        .catch(reject)
    })
  }

  const instantiateWhitelist = async () => {
    if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
    if (!whitelistContract) throw new Error('Contract not found')

    if (whitelistDetails?.whitelistType === 'standard' || whitelistDetails?.whitelistType === 'flex') {
      const standardMsg = {
        members: whitelistDetails.members,
        start_time: whitelistDetails.startTime,
        end_time: whitelistDetails.endTime,
        mint_price: coin(
          String(Number(whitelistDetails.unitPrice)),
          mintTokenFromFactory ? mintTokenFromFactory.denom : 'ustars',
        ),
        per_address_limit: whitelistDetails.perAddressLimit,
        member_limit: whitelistDetails.memberLimit,
        admins: whitelistDetails.admins || [wallet.address],
        admins_mutable: whitelistDetails.adminsMutable,
      }

      const flexMsg = {
        members: whitelistDetails.members,
        start_time: whitelistDetails.startTime,
        end_time: whitelistDetails.endTime,
        mint_price: coin(
          String(Number(whitelistDetails.unitPrice)),
          mintTokenFromFactory ? mintTokenFromFactory.denom : 'ustars',
        ),
        member_limit: whitelistDetails.memberLimit,
        admins: whitelistDetails.admins || [wallet.address],
        admins_mutable: whitelistDetails.adminsMutable,
      }

      const data = await whitelistContract.instantiate(
        whitelistDetails.whitelistType === 'standard' ? WHITELIST_CODE_ID : WHITELIST_FLEX_CODE_ID,
        whitelistDetails.whitelistType === 'standard' ? standardMsg : flexMsg,
        'Stargaze Whitelist Contract',
        wallet.address,
      )

      return data.contractAddress
    } else if (whitelistDetails?.whitelistType === 'merkletree') {
      const members = whitelistDetails.members as string[]
      const membersCsv = members.join('\n')
      const membersBlob = new Blob([membersCsv], { type: 'text/csv' })
      const membersFile = new File([membersBlob], 'members.csv', { type: 'text/csv' })
      const formData = new FormData()
      formData.append('whitelist', membersFile)
      const response = await toast
        .promise(
          axios.post(`${WHITELIST_MERKLE_TREE_API_URL}/create_whitelist`, formData, {
            headers: {
              'Content-Type': 'multipart/form-data',
            },
          }),
          {
            loading: 'Fetching merkle root hash...',
            success: 'Merkle root fetched successfully.',
            error: 'Error fetching root hash from Whitelist Merkle Tree API.',
          },
        )
        .catch((error) => {
          console.log('error', error)
          throw new Error('Whitelist instantiation failed.')
        })

      const rootHash = response.data.root_hash
      console.log('rootHash', rootHash)

      const merkleTreeMsg = {
        merkle_root: rootHash,
        merkle_tree_uri: null,
        start_time: whitelistDetails.startTime,
        end_time: whitelistDetails.endTime,
        mint_price: coin(
          String(Number(whitelistDetails.unitPrice)),
          mintTokenFromFactory ? mintTokenFromFactory.denom : 'ustars',
        ),
        per_address_limit: whitelistDetails.perAddressLimit,
        admins: whitelistDetails.admins || [wallet.address],
        admins_mutable: whitelistDetails.adminsMutable,
      }

      const data = await whitelistMerkleTreeContract?.instantiate(
        WHITELIST_MERKLE_TREE_CODE_ID,
        merkleTreeMsg,
        'Stargaze Whitelist Merkle Tree Contract',
        wallet.address,
      )
      return data?.contractAddress
    }
  }

  const instantiateOpenEditionMinter = async (
    uri: string,
    coverImageUri: string,
    thumbnailUri?: string,
    whitelist?: string,
  ) => {
    if (!wallet.isWalletConnected) throw new Error('Wallet not connected')
    if (!openEditionFactoryContract) throw new Error('Contract not found')
    if (!openEditionMinterContract) throw new Error('Contract not found')

    let royaltyInfo = null
    if (royaltyDetails?.royaltyType === 'new') {
      royaltyInfo = {
        payment_address: royaltyDetails.paymentAddress.trim(),
        share: (Number(royaltyDetails.share) / 100).toString(),
      }
    }

    const msg = {
      create_minter: {
        init_msg: {
          nft_data: {
            nft_data_type: metadataStorageMethod === 'off-chain' ? 'off_chain_metadata' : 'on_chain_metadata',
            token_uri: metadataStorageMethod === 'off-chain' ? uri : null,
            extension:
              metadataStorageMethod === 'on-chain'
                ? {
                    image:
                      imageUploadDetails?.isThumbnailCompatible && imageUploadDetails.thumbnailFile
                        ? thumbnailUri
                        : uri,
                    name: onChainMetadataInputDetails?.name,
                    description: onChainMetadataInputDetails?.description?.replaceAll('\\n', '\n'),
                    attributes: onChainMetadataInputDetails?.attributes,
                    external_url: onChainMetadataInputDetails?.external_url,
                    animation_url:
                      imageUploadDetails?.uploadMethod === 'existing'
                        ? onChainMetadataInputDetails?.animation_url
                        : imageUploadDetails?.isThumbnailCompatible
                        ? uri
                        : undefined,
                    youtube_url: onChainMetadataInputDetails?.youtube_url,
                  }
                : null,
          },
          start_time: mintingDetails?.startTime,
          end_time:
            mintingDetails?.limitType === ('time_limited' as LimitType) ||
            mintingDetails?.limitType === ('time_and_count_limited' as LimitType)
              ? mintingDetails.endTime
              : null,
          mint_price: {
            amount: Number(mintingDetails?.unitPrice).toString(),
            denom: (mintTokenFromFactory?.denom as string) || 'ustars',
          },
          per_address_limit: mintingDetails?.perAddressLimit,
          num_tokens:
            mintingDetails?.limitType === ('count_limited' as LimitType) ||
            mintingDetails?.limitType === ('time_and_count_limited' as LimitType)
              ? mintingDetails.tokenCountLimit
              : null,
          payment_address: mintingDetails?.paymentAddress || null,
          whitelist,
        },
        collection_params: {
          code_id: collectionDetails?.updatable
            ? SG721_OPEN_EDITION_UPDATABLE_CODE_ID
            : mintingDetails?.selectedMintToken?.displayName === 'USK' ||
              mintingDetails?.selectedMintToken?.displayName === 'USDC' ||
              mintingDetails?.selectedMintToken?.displayName === 'TIA' ||
              mintingDetails?.selectedMintToken?.displayName === 'STRDST' ||
              mintingDetails?.selectedMintToken?.displayName === 'KUJI' ||
              mintingDetails?.selectedMintToken?.displayName === 'HUAHUA' ||
              mintingDetails?.selectedMintToken?.displayName === 'BRNCH' ||
              mintingDetails?.selectedMintToken?.displayName === 'CRBRUS'
            ? STRDST_SG721_CODE_ID
            : SG721_OPEN_EDITION_CODE_ID,
          name: collectionDetails?.name,
          symbol: collectionDetails?.symbol,
          info: {
            creator: wallet.address,
            description: collectionDetails?.description.replaceAll('\\n', '\n'),
            image: coverImageUri,
            explicit_content: collectionDetails?.explicit || false,
            royalty_info: royaltyInfo,
            start_trading_time: collectionDetails?.startTradingTime || null,
          },
        },
      },
    }

    const payload: OpenEditionFactoryDispatchExecuteArgs = {
      contract: openEditionFactoryAddress as string,
      messages: openEditionFactoryMessages,
      txSigner: wallet.address || '',
      msg,
      funds: [coin(openEditionMinterCreationFee as string, 'ustars')],
      updatable: collectionDetails?.updatable,
    }
    await openEditionFactoryDispatchExecute(payload)
      .then((data) => {
        setTransactionHash(data.transactionHash)
        setOpenEditionMinterContractAddress(data.openEditionMinterAddress)
        setSg721ContractAddress(data.sg721Address)
      })
      .catch((error) => {
        toast.error(error.message, { style: { maxWidth: 'none' } })
        addLogItem({ id: uid(), message: error.message, type: 'Error', timestamp: new Date() })
        setUploading(false)
        setCreationInProgress(false)
      })
  }
  useEffect(() => {
    if (minterType !== 'openEdition') {
      setTransactionHash(null)
      setOpenEditionMinterContractAddress(null)
      setSg721ContractAddress(null)
      setCreationInProgress(false)
      setUploading(false)
    }
  }, [minterType])

  useEffect(() => {
    const data: OpenEditionMinterCreatorDataProps = {
      metadataStorageMethod,
      openEditionMinterContractAddress,
      sg721ContractAddress,
      whitelistContractAddress,
      transactionHash,
    }
    onChange(data)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    metadataStorageMethod,
    openEditionMinterContractAddress,
    sg721ContractAddress,
    whitelistContractAddress,
    transactionHash,
  ])

  useEffect(() => {
    const data: OpenEditionMinterDetailsDataProps = {
      imageUploadDetails: imageUploadDetails ? imageUploadDetails : undefined,
      collectionDetails: collectionDetails ? collectionDetails : undefined,
      whitelistDetails: whitelistDetails ? whitelistDetails : undefined,
      royaltyDetails: royaltyDetails ? royaltyDetails : undefined,
      onChainMetadataInputDetails: onChainMetadataInputDetails ? onChainMetadataInputDetails : undefined,
      offChainMetadataUploadDetails: offChainMetadataUploadDetails ? offChainMetadataUploadDetails : undefined,
      mintingDetails: mintingDetails ? mintingDetails : undefined,
      metadataStorageMethod,
      openEditionMinterContractAddress,
      coverImageUrl,
      tokenUri,
      tokenImageUri,
      isRefreshed,
    }
    onDetailsChange(data)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [
    imageUploadDetails,
    collectionDetails,
    whitelistDetails,
    royaltyDetails,
    onChainMetadataInputDetails,
    offChainMetadataUploadDetails,
    mintingDetails,
    metadataStorageMethod,
    openEditionMinterContractAddress,
    coverImageUrl,
    tokenUri,
    tokenImageUri,
    isRefreshed,
  ])

  useEffect(() => {
    if (importedOpenEditionMinterDetails) {
      setMetadataStorageMethod(importedOpenEditionMinterDetails.metadataStorageMethod as MetadataStorageMethod)
    }
  }, [importedOpenEditionMinterDetails])

  const fetchWhitelistConfig = async (contractAddress: string | undefined) => {
    if (contractAddress === '' || !whitelistDetails) return
    const contract = whitelistContract?.use(contractAddress)

    await contract
      ?.config()
      .then((config) => {
        if (!config) {
          whitelistDetails.whitelistType = 'standard'
          return
        }

        if (JSON.stringify(config).includes('whale_cap')) whitelistDetails.whitelistType = 'flex'
        else if (!JSON.stringify(config).includes('member_limit') || config.member_limit === 0) {
          // whitelistDetails.whitelistType = 'merkletree'
          toast.error(
            'Whitelist Merkle Tree is not supported yet for open edition collections. Please use a standard or flexible whitelist contract.',
          )
        } else whitelistDetails.whitelistType = 'standard'
        setIsRefreshed(!isRefreshed)
      })
      .catch((error) => {
        console.log('error', error)
      })
  }

  const debouncedWhitelistContractAddress = useDebounce(whitelistDetails?.contractAddress, 300)

  useEffect(() => {
    if (whitelistDetails?.whitelistState === 'existing' && debouncedWhitelistContractAddress !== '') {
      void fetchWhitelistConfig(debouncedWhitelistContractAddress)
    }
  }, [whitelistDetails?.whitelistState, debouncedWhitelistContractAddress])

  return (
    <div>
      {/* TODO: Cancel once we're able to index on-chain metadata */}
      <Conditional test={false}>
        <div className="mx-10 mb-4 rounded border-2 border-white/20">
          <div className="flex justify-center mb-2">
            <div className="mt-3 ml-4 font-bold form-check form-check-inline">
              <input
                checked={metadataStorageMethod === 'off-chain'}
                className="peer sr-only"
                id="inlineRadio9"
                name="inlineRadioOptions9"
                onClick={() => {
                  setMetadataStorageMethod('off-chain')
                }}
                type="radio"
                value="Off Chain"
              />
              <label
                className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black peer-checked:border-b-2 hover:border-b-2  peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
                htmlFor="inlineRadio9"
              >
                Off-Chain Metadata
              </label>
            </div>
            <div className="mt-3 ml-2 font-bold form-check form-check-inline">
              <input
                checked={metadataStorageMethod === 'on-chain'}
                className="peer sr-only"
                id="inlineRadio10"
                name="inlineRadioOptions10"
                onClick={() => {
                  setMetadataStorageMethod('on-chain')
                }}
                type="radio"
                value="On Chain"
              />
              <label
                className="inline-block py-1 px-2 text-gray peer-checked:text-white hover:text-white peer-checked:bg-black peer-checked:border-b-2 hover:border-b-2  peer-checked:border-plumbus hover:border-plumbus cursor-pointer form-check-label"
                htmlFor="inlineRadio10"
              >
                On-Chain Metadata
              </label>
            </div>
          </div>
        </div>
      </Conditional>
      <div className={clsx('my-0 mx-10')}>
        <Conditional test={metadataStorageMethod === 'off-chain'}>
          <div>
            <OffChainMetadataUploadDetails
              importedOffChainMetadataUploadDetails={importedOpenEditionMinterDetails?.offChainMetadataUploadDetails}
              onChange={setOffChainMetadataUploadDetails}
            />
          </div>
        </Conditional>
        <Conditional test={metadataStorageMethod === 'on-chain'}>
          <div>
            <ImageUploadDetails
              importedImageUploadDetails={importedOpenEditionMinterDetails?.imageUploadDetails}
              onChange={setImageUploadDetails}
            />
            <OnChainMetadataInputDetails
              importedOnChainMetadataInputDetails={importedOpenEditionMinterDetails?.onChainMetadataInputDetails}
              onChange={setOnChainMetadataInputDetails}
              uploadMethod={imageUploadDetails?.uploadMethod}
            />
          </div>
        </Conditional>
      </div>
      <div className="flex justify-between py-3 px-8 mx-10 rounded border-2 border-white/20 grid-col-2">
        <CollectionDetails
          coverImageUrl={
            metadataStorageMethod === 'off-chain'
              ? (offChainMetadataUploadDetails?.imageUrl as string)
              : (imageUploadDetails?.coverImageUrl as string)
          }
          importedCollectionDetails={importedOpenEditionMinterDetails?.collectionDetails}
          metadataStorageMethod={metadataStorageMethod}
          onChange={setCollectionDetails}
          uploadMethod={
            metadataStorageMethod === 'off-chain'
              ? (offChainMetadataUploadDetails?.uploadMethod as UploadMethod)
              : (imageUploadDetails?.uploadMethod as UploadMethod)
          }
        />
        <MintingDetails
          importedMintingDetails={importedOpenEditionMinterDetails?.mintingDetails}
          isPresale={whitelistDetails?.whitelistState === 'new'}
          minimumMintPrice={Number(minimumMintPrice) / 1000000}
          mintTokenFromFactory={mintTokenFromFactory}
          onChange={setMintingDetails}
          uploadMethod={offChainMetadataUploadDetails?.uploadMethod as UploadMethod}
          whitelistStartDate={whitelistDetails?.startTime}
        />
      </div>

      <div className="my-6 mx-10">
        <WhitelistDetails
          importedWhitelistDetails={importedOpenEditionMinterDetails?.whitelistDetails}
          mintingTokenFromFactory={mintTokenFromFactory}
          onChange={setWhitelistDetails}
        />
      </div>

      <div className="my-6">
        <RoyaltyDetails
          importedRoyaltyDetails={importedOpenEditionMinterDetails?.royaltyDetails}
          onChange={setRoyaltyDetails}
        />
      </div>
      <div className="flex justify-end w-full">
        <Button
          className="relative justify-center p-2 mr-12 mb-6 max-h-12 text-white bg-plumbus hover:bg-plumbus-light border-0"
          isLoading={creationInProgress}
          onClick={performOpenEditionMinterChecks}
          variant="solid"
        >
          Create Collection
        </Button>
      </div>
      <Conditional test={uploading}>
        <LoadingModal />
      </Conditional>
      <Conditional test={readyToCreate}>
        <ConfirmationModal confirm={createOpenEditionMinter} />
      </Conditional>
    </div>
  )
}
