import { createMintNewEditionFromMasterEditionViaTokenInstruction } from '@metaplex-solarti/mpl-token-metadata';
import { Keypair } from '@solarti/web3.js';
import { toOriginalEditionAccount } from '../accounts.mjs';
import { toNftOriginalEdition } from '../models/NftEdition.mjs';
import { useOperation, makeConfirmOptionsFinalizedOnMainnet } from '../../../types/Operation.mjs';
import { assertNftWithToken } from '../models/Nft.mjs';
import { toBigNumber } from '../../../types/BigNumber.mjs';
import { token } from '../../../types/Amount.mjs';
import { TransactionBuilder } from '../../../utils/TransactionBuilder.mjs';

// -----------------
// Operation
// -----------------

const Key = 'PrintNewEditionOperation';

/**
 * Prints a new edition from an original NFT.
 *
 * ```ts
 * const { nft } = await metaplex
 *   .nfts()
 *   .printNewEdition({ originalMint };
 * ```
 *
 * @group Operations
 * @category Constructors
 */
const printNewEditionOperation = useOperation(Key);

/**
 * @group Operations
 * @category Types
 */

/**
 * @group Operations
 * @category Handlers
 */
const printNewEditionOperationHandler = {
  handle: async (operation, metaplex, scope) => {
    const originalEditionAccount = await metaplex.rpc().getAccount(metaplex.nfts().pdas().masterEdition({
      mint: operation.input.originalMint,
      programs: scope.programs
    }));
    scope.throwIfCanceled();
    const originalEdition = toNftOriginalEdition(toOriginalEditionAccount(originalEditionAccount));
    const builder = await printNewEditionBuilder(metaplex, {
      ...operation.input,
      originalSupply: originalEdition.supply
    }, scope);
    scope.throwIfCanceled();
    const confirmOptions = makeConfirmOptionsFinalizedOnMainnet(metaplex, scope.confirmOptions);
    const output = await builder.sendAndConfirm(metaplex, confirmOptions);
    scope.throwIfCanceled();
    const nft = await metaplex.nfts().findByMint({
      mintAddress: output.mintSigner.publicKey,
      tokenAddress: output.tokenAddress
    }, scope);
    scope.throwIfCanceled();
    assertNftWithToken(nft);
    return {
      ...output,
      nft
    };
  }
};

// -----------------
// Builder
// -----------------

/**
 * @group Transaction Builders
 * @category Inputs
 */

/**
 * Prints a new edition from an original NFT.
 *
 * ```ts
 * const transactionBuilder = await metaplex
 *   .nfts()
 *   .builders()
 *   .printNewEdition({ originalMint });
 * ```
 *
 * @group Transaction Builders
 * @category Constructors
 */
const printNewEditionBuilder = async (metaplex, params, options = {}) => {
  const {
    programs,
    payer = metaplex.rpc().getDefaultFeePayer()
  } = options;
  const {
    originalMint,
    newMint = Keypair.generate(),
    newUpdateAuthority = metaplex.identity().publicKey,
    newOwner = metaplex.identity().publicKey,
    newTokenAccount,
    printNewEditionInstructionKey = 'printNewEdition'
  } = params;

  // Programs.
  const tokenMetadataProgram = metaplex.programs().getTokenMetadata(programs);

  // Original NFT.
  const originalMetadataAddress = metaplex.nfts().pdas().metadata({
    mint: originalMint,
    programs
  });
  const originalEditionAddress = metaplex.nfts().pdas().masterEdition({
    mint: originalMint,
    programs
  });
  const edition = toBigNumber(params.originalSupply.addn(1));
  const originalEditionMarkPda = metaplex.nfts().pdas().editionMarker({
    mint: originalMint,
    edition,
    programs
  });

  // New NFT.
  const newMintAuthority = Keypair.generate(); // Will be overwritten by edition PDA.
  const newMetadataAddress = metaplex.nfts().pdas().metadata({
    mint: newMint.publicKey,
    programs
  });
  const newEditionAddress = metaplex.nfts().pdas().edition({
    mint: newMint.publicKey,
    programs
  });
  const sharedAccounts = {
    newMetadata: newMetadataAddress,
    newEdition: newEditionAddress,
    masterEdition: originalEditionAddress,
    newMint: newMint.publicKey,
    editionMarkPda: originalEditionMarkPda,
    newMintAuthority: newMintAuthority.publicKey,
    payer: payer.publicKey,
    newMetadataUpdateAuthority: newUpdateAuthority,
    metadata: originalMetadataAddress
  };
  const tokenWithMintBuilder = await metaplex.tokens().builders().createTokenWithMint({
    decimals: 0,
    initialSupply: token(1),
    mint: newMint,
    mintAuthority: newMintAuthority,
    freezeAuthority: newMintAuthority.publicKey,
    owner: newOwner,
    token: newTokenAccount,
    createMintAccountInstructionKey: params.createMintAccountInstructionKey,
    initializeMintInstructionKey: params.initializeMintInstructionKey,
    createAssociatedTokenAccountInstructionKey: params.createAssociatedTokenAccountInstructionKey,
    createTokenAccountInstructionKey: params.createTokenAccountInstructionKey,
    initializeTokenInstructionKey: params.initializeTokenInstructionKey,
    mintTokensInstructionKey: params.mintTokensInstructionKey
  }, {
    payer,
    programs
  });
  const {
    tokenAddress
  } = tokenWithMintBuilder.getContext();
  const originalTokenAccountOwner = params.originalTokenAccountOwner ?? metaplex.identity();
  const originalTokenAccount = params.originalTokenAccount ?? metaplex.tokens().pdas().associatedTokenAccount({
    mint: originalMint,
    owner: originalTokenAccountOwner.publicKey,
    programs
  });
  return TransactionBuilder.make().setFeePayer(payer).setContext({
    mintSigner: newMint,
    metadataAddress: newMetadataAddress,
    editionAddress: newEditionAddress,
    tokenAddress,
    updatedSupply: edition
  })

  // Create the mint and token accounts before minting 1 token to the owner.
  .add(tokenWithMintBuilder)

  // Mint new edition.
  .add({
    instruction: createMintNewEditionFromMasterEditionViaTokenInstruction({
      ...sharedAccounts,
      tokenAccountOwner: originalTokenAccountOwner.publicKey,
      tokenAccount: originalTokenAccount
    }, {
      mintNewEditionFromMasterEditionViaTokenArgs: {
        edition
      }
    }, tokenMetadataProgram.address),
    signers: [newMint, newMintAuthority, payer, originalTokenAccountOwner],
    key: printNewEditionInstructionKey
  });
};

export { printNewEditionBuilder, printNewEditionOperation, printNewEditionOperationHandler };
//# sourceMappingURL=printNewEdition.mjs.map
