import { createUpdateCandyMachineInstruction, createSetCollectionInstruction, createRemoveCollectionInstruction, createUpdateAuthorityInstruction } from '@metaplex-solarti/mpl-candy-machine';
import isEqual from 'lodash.isequal';
import { findCandyMachineV2CollectionPda } from '../pdas.mjs';
import { toCandyMachineV2Configs, toCandyMachineV2InstructionData } from '../models/CandyMachineV2.mjs';
import { useOperation } from '../../../types/Operation.mjs';
import { NoInstructionsToSendError } from '../../../errors/SdkError.mjs';
import { TransactionBuilder } from '../../../utils/TransactionBuilder.mjs';

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

const Key = 'UpdateCandyMachineV2Operation';

/**
 * Updates an existing Candy Machine.
 *
 * ```ts
 * await metaplex
 *   .candyMachinesV2()
 *   .update({
 *     candyMachine,
 *     price: sol(2), // Updates the price only.
 *   };
 * ```
 *
 * @group Operations
 * @category Constructors
 */
const updateCandyMachineV2Operation = useOperation(Key);

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

/**
 * @group Operations
 * @category Handlers
 */
const updateCandyMachineV2OperationHandler = {
  async handle(operation, metaplex, scope) {
    const {
      candyMachine,
      authority = metaplex.identity(),
      newAuthority,
      newCollection,
      ...updatableFields
    } = operation.input;
    const currentConfigs = toCandyMachineV2Configs(candyMachine);
    const instructionDataWithoutChanges = toCandyMachineV2InstructionData(candyMachine.address, currentConfigs);
    const instructionData = toCandyMachineV2InstructionData(candyMachine.address, {
      ...currentConfigs,
      ...updatableFields
    });
    const {
      data,
      wallet,
      tokenMint
    } = instructionData;
    const shouldUpdateData = !isEqual(instructionData, instructionDataWithoutChanges);
    const builder = updateCandyMachineV2Builder(metaplex, {
      candyMachine,
      authority,
      newData: shouldUpdateData ? {
        ...data,
        wallet,
        tokenMint
      } : undefined,
      newCollection,
      newAuthority
    }, scope);
    if (builder.isEmpty()) {
      throw new NoInstructionsToSendError(Key);
    }
    return builder.sendAndConfirm(metaplex, scope.confirmOptions);
  }
};

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

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

/**
 * Updates an existing Candy Machine.
 *
 * ```ts
 * const transactionBuilder = metaplex
 *   .candyMachinesV2()
 *   .builders()
 *   .update({
 *     candyMachine: { address, walletAddress, collectionMintAddress },
 *     newData: {...}, // Updates the provided data.
 *   });
 * ```
 *
 * @group Transaction Builders
 * @category Constructors
 */
const updateCandyMachineV2Builder = (metaplex, params, options = {}) => {
  const {
    programs,
    payer = metaplex.rpc().getDefaultFeePayer()
  } = options;
  const {
    candyMachine,
    authority = metaplex.identity(),
    newData,
    newAuthority,
    newCollection
  } = params;
  const tokenMetadataProgram = metaplex.programs().getTokenMetadata(programs).address;
  const shouldUpdateAuthority = !!newAuthority && !newAuthority.equals(authority.publicKey);
  const sameCollection = newCollection && candyMachine.collectionMintAddress && candyMachine.collectionMintAddress.equals(newCollection);
  const shouldUpdateCollection = !!newCollection && !sameCollection;
  const shouldRemoveCollection = !shouldUpdateCollection && newCollection === null && candyMachine.collectionMintAddress !== null;
  return TransactionBuilder.make().setFeePayer(payer)

  // Update data.
  .when(!!newData, builder => {
    const data = newData;
    const wallet = newData?.wallet;
    const tokenMint = newData?.tokenMint;
    const updateInstruction = createUpdateCandyMachineInstruction({
      candyMachine: candyMachine.address,
      authority: authority.publicKey,
      wallet
    }, {
      data
    });
    if (tokenMint) {
      updateInstruction.keys.push({
        pubkey: tokenMint,
        isWritable: false,
        isSigner: false
      });
    }
    return builder.add({
      instruction: updateInstruction,
      signers: [authority],
      key: params.updateInstructionKey ?? 'update'
    });
  })

  // Set or update collection.
  .when(shouldUpdateCollection, builder => {
    const collectionMint = newCollection;
    const metadata = metaplex.nfts().pdas().metadata({
      mint: collectionMint,
      programs
    });
    const edition = metaplex.nfts().pdas().masterEdition({
      mint: collectionMint,
      programs
    });
    const collectionPda = findCandyMachineV2CollectionPda(candyMachine.address);
    const collectionAuthorityRecord = metaplex.nfts().pdas().collectionAuthorityRecord({
      mint: collectionMint,
      collectionAuthority: collectionPda,
      programs
    });
    return builder.add({
      instruction: createSetCollectionInstruction({
        candyMachine: candyMachine.address,
        authority: authority.publicKey,
        collectionPda,
        payer: payer.publicKey,
        metadata,
        mint: collectionMint,
        edition,
        collectionAuthorityRecord,
        tokenMetadataProgram
      }),
      signers: [payer, authority],
      key: params.setCollectionInstructionKey ?? 'setCollection'
    });
  })

  // Remove collection.
  .when(shouldRemoveCollection, builder => {
    const collectionMint = candyMachine.collectionMintAddress;
    const metadata = metaplex.nfts().pdas().metadata({
      mint: collectionMint,
      programs
    });
    const collectionPda = findCandyMachineV2CollectionPda(candyMachine.address);
    const collectionAuthorityRecord = metaplex.nfts().pdas().collectionAuthorityRecord({
      mint: collectionMint,
      collectionAuthority: collectionPda,
      programs
    });
    return builder.add({
      instruction: createRemoveCollectionInstruction({
        candyMachine: candyMachine.address,
        authority: authority.publicKey,
        collectionPda,
        metadata,
        mint: collectionMint,
        collectionAuthorityRecord,
        tokenMetadataProgram
      }),
      signers: [authority],
      key: params.removeCollectionInstructionKey ?? 'removeCollection'
    });
  })

  // Update authority.
  .when(shouldUpdateAuthority, builder => builder.add({
    instruction: createUpdateAuthorityInstruction({
      candyMachine: candyMachine.address,
      authority: authority.publicKey,
      wallet: newData?.wallet ?? candyMachine.walletAddress
    }, {
      newAuthority: newAuthority
    }),
    signers: [authority],
    key: params.updateAuthorityInstructionKey ?? 'updateAuthority'
  }));
};

export { updateCandyMachineV2Builder, updateCandyMachineV2Operation, updateCandyMachineV2OperationHandler };
//# sourceMappingURL=updateCandyMachineV2.mjs.map
