import { createUpdateInstruction, createSetCollectionInstruction, createSetMintAuthorityInstruction, createSetAuthorityInstruction } from '@metaplex-foundation/mpl-candy-machine-core';
import { removeUndefinedAttributes } from '../../../utils/common.mjs';
import { isCandyMachine, toCandyMachineData } from '../models/CandyMachine.mjs';
import { assertObjectHasDefinedKeys } from '../../../utils/assert.mjs';
import { NoInstructionsToSendError, MissingInputDataError } from '../../../errors/SdkError.mjs';
import { TransactionBuilder } from '../../../utils/TransactionBuilder.mjs';
import { toPublicKey } from '../../../types/PublicKey.mjs';

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

const Key = 'UpdateCandyMachineOperation';

/**
 * Updates the every aspect of an existing Candy Machine, including its
 * authorities, collection and guards (when associated with a Candy Guard).
 *
 * ```ts
 * await metaplex
 *   .candyMachines()
 *   .update({
 *     candyMachine,
 *     sellerFeeBasisPoints: 500,
 *   };
 * ```
 *
 * @group Operations
 * @category Constructors
 */
const updateCandyMachineOperation = _updateCandyMachineOperation;
// eslint-disable-next-line @typescript-eslint/naming-convention
function _updateCandyMachineOperation(input) {
  return {
    key: Key,
    input
  };
}
_updateCandyMachineOperation.key = Key;

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

/**
 * @group Operations
 * @category Handlers
 */
const updateCandyMachineOperationHandler = {
  async handle(operation, metaplex, scope) {
    const builder = updateCandyMachineBuilder(metaplex, operation.input, scope);
    if (builder.isEmpty()) {
      throw new NoInstructionsToSendError(Key);
    }
    return builder.sendAndConfirm(metaplex, scope.confirmOptions);
  }
};

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

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

/**
 * Updates the every aspect of an existing Candy Machine, including its
 * authorities, collection and guards (when associated with a Candy Guard).
 *
 * ```ts
 * const transactionBuilder = await metaplex
 *   .candyMachines()
 *   .builders()
 *   .update({
 *     candyMachine,
 *     sellerFeeBasisPoints: 500,
 *   });
 * ```
 *
 * @group Transaction Builders
 * @category Constructors
 */
const updateCandyMachineBuilder = (metaplex, params, options = {}) => {
  const {
    programs,
    payer = metaplex.rpc().getDefaultFeePayer()
  } = options;
  const {
    authority = metaplex.identity(),
    candyGuardAuthority = authority
  } = params;
  return TransactionBuilder.make().setFeePayer(payer)

  // Update Candy Machine data.
  .add(updateCandyMachineDataBuilder(metaplex, params, authority, programs))

  // Update Candy Machine collection.
  .add(updateCandyMachineCollectionBuilder(metaplex, params, authority, payer, programs))

  // Update Candy Guard's guards and groups, if any.
  .add(updateCandyGuardsBuilder(metaplex, params, candyGuardAuthority, payer, programs))

  // Update Candy Machine mint authority.
  .add(updateCandyMachineMintAuthorityBuilder(metaplex, params, authority, programs))

  // Update Candy Machine authority.
  .add(updateCandyMachineAuthorityBuilder(metaplex, params, authority, programs));
};
const updateCandyMachineDataBuilder = (metaplex, params, authority, programs) => {
  const dataToUpdate = removeUndefinedAttributes({
    itemsAvailable: params.itemsAvailable,
    symbol: params.symbol,
    sellerFeeBasisPoints: params.sellerFeeBasisPoints,
    maxEditionSupply: params.maxEditionSupply,
    isMutable: params.isMutable,
    creators: params.creators,
    itemSettings: params.itemSettings
  });
  const candyMachineProgram = metaplex.programs().getCandyMachine(programs);
  let data;
  if (Object.keys(dataToUpdate).length === 0) {
    return TransactionBuilder.make();
  } else if (isCandyMachine(params.candyMachine)) {
    data = toCandyMachineData({
      ...params.candyMachine,
      ...dataToUpdate
    });
  } else {
    assertObjectHasDefinedKeys(dataToUpdate, ['itemsAvailable', 'symbol', 'sellerFeeBasisPoints', 'maxEditionSupply', 'isMutable', 'creators', 'itemSettings'], onMissingInputError);
    data = toCandyMachineData(dataToUpdate);
  }
  return TransactionBuilder.make().add({
    instruction: createUpdateInstruction({
      candyMachine: toPublicKey(params.candyMachine),
      authority: authority.publicKey
    }, {
      data
    }, candyMachineProgram.address),
    signers: [authority],
    key: params.updateDataInstructionKey ?? 'updateCandyMachineData'
  });
};
const updateCandyMachineCollectionBuilder = (metaplex, params, authority, payer, programs) => {
  if (!params.collection) {
    return TransactionBuilder.make();
  }
  const currentCollectionAddress = params.collection.currentCollectionAddress ?? (isCandyMachine(params.candyMachine) ? params.candyMachine.collectionMintAddress : null);
  if (!currentCollectionAddress) {
    throw onMissingInputError(['collection.currentCollectionAddress']);
  }

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

  // Addresses.
  const candyMachineAddress = toPublicKey(params.candyMachine);
  const collectionAddress = params.collection.address;
  const collectionUpdateAuthority = params.collection.updateAuthority;

  // PDAs.
  const authorityPda = metaplex.candyMachines().pdas().authority({
    candyMachine: candyMachineAddress,
    programs
  });
  const currentCollectionMetadata = metaplex.nfts().pdas().metadata({
    mint: currentCollectionAddress
  });
  const currentCollectionAuthorityRecord = metaplex.nfts().pdas().collectionAuthorityRecord({
    mint: currentCollectionAddress,
    collectionAuthority: authorityPda
  });
  const collectionMetadata = metaplex.nfts().pdas().metadata({
    mint: collectionAddress
  });
  const collectionMasterEdition = metaplex.nfts().pdas().masterEdition({
    mint: collectionAddress
  });
  const collectionAuthorityRecord = metaplex.nfts().pdas().collectionAuthorityRecord({
    mint: collectionAddress,
    collectionAuthority: authorityPda
  });
  return TransactionBuilder.make().add({
    instruction: createSetCollectionInstruction({
      candyMachine: candyMachineAddress,
      authority: authority.publicKey,
      authorityPda,
      payer: payer.publicKey,
      collectionMint: currentCollectionAddress,
      collectionMetadata: currentCollectionMetadata,
      collectionAuthorityRecord: currentCollectionAuthorityRecord,
      newCollectionUpdateAuthority: collectionUpdateAuthority.publicKey,
      newCollectionMetadata: collectionMetadata,
      newCollectionMint: collectionAddress,
      newCollectionMasterEdition: collectionMasterEdition,
      newCollectionAuthorityRecord: collectionAuthorityRecord,
      tokenMetadataProgram: tokenMetadataProgram.address
    }, candyMachineProgram.address),
    signers: [authority, payer, collectionUpdateAuthority],
    key: params.setCollectionInstructionKey ?? 'setCandyMachineCollection'
  });
};
const updateCandyGuardsBuilder = (metaplex, params, candyGuardAuthority, payer, programs) => {
  const guardsToUpdate = removeUndefinedAttributes({
    candyGuard: params.candyGuard,
    guards: params.guards,
    groups: params.groups
  });
  let args;
  if (Object.keys(guardsToUpdate).length === 0) {
    return TransactionBuilder.make();
  }
  if (isCandyMachine(params.candyMachine) && params.candyMachine.candyGuard) {
    args = {
      candyGuard: params.candyMachine.candyGuard.address,
      guards: params.candyMachine.candyGuard.guards,
      groups: params.candyMachine.candyGuard.groups,
      ...guardsToUpdate
    };
  } else {
    assertObjectHasDefinedKeys(guardsToUpdate, ['candyGuard', 'guards', 'groups'], onMissingInputError);
    args = guardsToUpdate;
  }
  return metaplex.candyMachines().builders().updateCandyGuard({
    candyGuard: args.candyGuard,
    guards: args.guards,
    groups: args.groups,
    authority: candyGuardAuthority,
    updateInstructionKey: params.updateCandyGuardInstructionKey ?? 'updateCandyGuard'
  }, {
    payer,
    programs
  });
};
const updateCandyMachineMintAuthorityBuilder = (metaplex, params, authority, programs) => {
  if (!params.newMintAuthority) {
    return TransactionBuilder.make();
  }
  const candyMachineProgram = metaplex.programs().getCandyMachine(programs);
  return TransactionBuilder.make().add({
    instruction: createSetMintAuthorityInstruction({
      candyMachine: toPublicKey(params.candyMachine),
      authority: authority.publicKey,
      mintAuthority: params.newMintAuthority.publicKey
    }, candyMachineProgram.address),
    signers: [authority, params.newMintAuthority],
    key: params.setAuthorityInstructionKey ?? 'setCandyMachineAuthority'
  });
};
const updateCandyMachineAuthorityBuilder = (metaplex, params, authority, programs) => {
  if (!params.newAuthority) {
    return TransactionBuilder.make();
  }
  const candyMachineProgram = metaplex.programs().getCandyMachine(programs);
  return TransactionBuilder.make().add({
    instruction: createSetAuthorityInstruction({
      candyMachine: toPublicKey(params.candyMachine),
      authority: authority.publicKey
    }, {
      newAuthority: params.newAuthority
    }, candyMachineProgram.address),
    signers: [authority],
    key: params.setAuthorityInstructionKey ?? 'setCandyMachineAuthority'
  });
};
const onMissingInputError = missingKeys => new MissingInputDataError(missingKeys, {
  problem: 'When passing the Candy Machine as a `PublicKey` instead of a Candy Machine model ' + 'the SDK cannot rely on current data to fill the gaps within the provided input.',
  solutionSuffix: ' Alternatively, you can pass the Candy Machine model instead.'
});

export { updateCandyMachineBuilder, updateCandyMachineOperation, updateCandyMachineOperationHandler };
//# sourceMappingURL=updateCandyMachine.mjs.map
