import { createInitializeInstruction } from '@metaplex-foundation/mpl-candy-machine-core';
import { Keypair } from '@solarti/web3.js';
import { toCandyMachineData, getCandyMachineSize } from '../models/CandyMachine.mjs';
import { makeConfirmOptionsFinalizedOnMainnet } from '../../../types/Operation.mjs';
import { toBigNumber } from '../../../types/BigNumber.mjs';
import { toPublicKey } from '../../../types/PublicKey.mjs';
import { TransactionBuilder } from '../../../utils/TransactionBuilder.mjs';
import { isSigner } from '../../../types/Signer.mjs';
import { ExpectedSignerError } from '../../../errors/SdkError.mjs';

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

const Key = 'CreateCandyMachineOperation';

/**
 * Creates a brand new Candy Machine with the provided settings.
 *
 * Unless the `withoutCandyGuard` option is set to `true`, a
 * Candy Guard will be created with the given guards and
 * immediately linked to the Candy Machine.
 *
 * ```ts
 *  const { candyMachine } = await metaplex
 *    .candyMachines()
 *    .create({
 *      itemsAvailable: toBigNumber(5000),
 *      sellerFeeBasisPoints: 333, // 3.33%
 *      collection: {
 *        address: collectionNft.address,
 *        updateAuthority: collectionUpdateAuthority,
 *      },
 *    });
 * ```
 *
 * @group Operations
 * @category Constructors
 */
const createCandyMachineOperation = _createCandyMachineOperation;
// eslint-disable-next-line @typescript-eslint/naming-convention
function _createCandyMachineOperation(input) {
  return {
    key: Key,
    input
  };
}
_createCandyMachineOperation.key = Key;

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

/**
 * @group Operations
 * @category Handlers
 */
const createCandyMachineOperationHandler = {
  async handle(operation, metaplex, scope) {
    const builder = await createCandyMachineBuilder(metaplex, operation.input, scope);
    scope.throwIfCanceled();
    const confirmOptions = makeConfirmOptionsFinalizedOnMainnet(metaplex, scope.confirmOptions);
    const output = await builder.sendAndConfirm(metaplex, confirmOptions);
    scope.throwIfCanceled();
    const candyMachine = await metaplex.candyMachines().findByAddress({
      address: output.candyMachineSigner.publicKey
    }, scope);
    scope.throwIfCanceled();
    return {
      ...output,
      candyMachine
    };
  }
};

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

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

/**

 * Creates a brand new Candy Machine with the provided settings.
 *
 * Unless the `withoutCandyGuard` option is set to `true`, a
 * Candy Guard will be created with the given guards and
 * immediately linked to the Candy Machine.
 *
 * ```ts
 *  const transactionBuilder = await metaplex
 *    .candyMachines()
 *    .builders()
 *    .create({
 *      itemsAvailable: toBigNumber(5000),
 *      sellerFeeBasisPoints: 333, // 3.33%
 *      collection: {
 *        address: collectionNft.address,
 *        updateAuthority: collectionUpdateAuthority,
 *      },
 *    });
 * ```
 *
 * @group Transaction Builders
 * @category Constructors
 */
const createCandyMachineBuilder = async (metaplex, params, options = {}) => {
  // Input.
  const {
    programs,
    payer = metaplex.rpc().getDefaultFeePayer()
  } = options;
  const {
    candyMachine = Keypair.generate(),
    authority = metaplex.identity(),
    collection,
    sellerFeeBasisPoints,
    itemsAvailable,
    symbol = '',
    maxEditionSupply = toBigNumber(0),
    isMutable = true,
    withoutCandyGuard = false
  } = params;
  const creators = params.creators ?? [{
    address: toPublicKey(authority),
    share: 100
  }];
  const itemSettings = params.itemSettings ?? {
    type: 'configLines',
    prefixName: '',
    nameLength: 32,
    prefixUri: '',
    uriLength: 200,
    isSequential: false
  };

  // PDAs.
  const authorityPda = metaplex.candyMachines().pdas().authority({
    candyMachine: candyMachine.publicKey,
    programs
  });
  const collectionMetadata = metaplex.nfts().pdas().metadata({
    mint: collection.address,
    programs
  });
  const collectionMasterEdition = metaplex.nfts().pdas().masterEdition({
    mint: collection.address,
    programs
  });
  const collectionAuthorityRecord = metaplex.nfts().pdas().collectionAuthorityRecord({
    mint: collection.address,
    collectionAuthority: authorityPda,
    programs
  });

  // Programs.
  const candyMachineProgram = metaplex.programs().getCandyMachine(programs);
  const tokenMetadataProgram = metaplex.programs().getTokenMetadata(programs);
  const candyMachineData = toCandyMachineData({
    itemsAvailable,
    symbol,
    sellerFeeBasisPoints,
    maxEditionSupply,
    isMutable,
    creators,
    itemSettings
  });
  const builder = TransactionBuilder.make().setFeePayer(payer).setContext({
    candyMachineSigner: candyMachine
  });
  let mintAuthority = toPublicKey(authority);
  if (!withoutCandyGuard) {
    const createCandyGuard = metaplex.candyMachines().builders().createCandyGuard({
      base: candyMachine,
      authority: toPublicKey(authority),
      guards: params.guards ?? {},
      groups: params.groups
    }, {
      programs,
      payer
    });
    const {
      candyGuardAddress
    } = createCandyGuard.getContext();
    mintAuthority = candyGuardAddress;
    builder.add(createCandyGuard);
  }
  return builder.add(await metaplex.system().builders().createAccount({
    space: getCandyMachineSize(candyMachineData),
    newAccount: candyMachine,
    program: candyMachineProgram.address
  }, {
    payer,
    programs
  })).add({
    instruction: createInitializeInstruction({
      candyMachine: candyMachine.publicKey,
      authorityPda,
      authority: toPublicKey(authority),
      payer: payer.publicKey,
      collectionMetadata,
      collectionMint: collection.address,
      collectionMasterEdition,
      collectionUpdateAuthority: collection.updateAuthority.publicKey,
      collectionAuthorityRecord,
      tokenMetadataProgram: tokenMetadataProgram.address
    }, {
      data: candyMachineData
    }, candyMachineProgram.address),
    signers: [payer, candyMachine, collection.updateAuthority],
    key: params.initializeCandyMachineInstructionKey ?? 'initializeCandyMachine'
  }).when(!withoutCandyGuard, builder => {
    if (!isSigner(authority)) {
      throw new ExpectedSignerError('authority', 'PublicKey', {
        problemSuffix: 'In order to create a Candy Machine with an associated ' + 'Candy Guard you must provide the authority as a Signer.'
      });
    }
    return builder.add(metaplex.candyMachines().builders().wrapCandyGuard({
      candyMachine: candyMachine.publicKey,
      candyMachineAuthority: authority,
      candyGuard: mintAuthority,
      candyGuardAuthority: authority,
      wrapCandyGuardInstructionKey: params.wrapCandyGuardInstructionKey
    }, {
      payer,
      programs
    }));
  });
};

export { createCandyMachineBuilder, createCandyMachineOperation, createCandyMachineOperationHandler };
//# sourceMappingURL=createCandyMachine.mjs.map
