import { createUpdateAuctionHouseInstruction, createDelegateAuctioneerInstruction, createUpdateAuctioneerInstruction } from '@metaplex-foundation/mpl-auction-house';
import isEqual from 'lodash.isequal';
import { AUCTIONEER_ALL_SCOPES } from '../constants.mjs';
import { TreasuryDestinationOwnerRequiredError } from '../errors.mjs';
import { assertAuctioneerAuctionHouse } from '../models/AuctionHouse.mjs';
import { useOperation } from '../../../types/Operation.mjs';
import { NoInstructionsToSendError } from '../../../errors/SdkError.mjs';
import { TransactionBuilder } from '../../../utils/TransactionBuilder.mjs';

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

const Key = 'UpdateAuctionHouseOperation';

/**
 * Updates an existing Auction House.
 *
 * ```ts
 * await metaplex
 *   .autionHouse()
 *   .update({
 *     auctionHouse,
 *     canChangeSalePrice: true, // Updates the canChangeSalePrice only.
 *   };
 * ```
 *
 * @group Operations
 * @category Constructors
 */
const updateAuctionHouseOperation = useOperation(Key);

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

/**
 * @group Operations
 * @category Handlers
 */
const updateAuctionHouseOperationHandler = {
  async handle(operation, metaplex, scope) {
    const {
      auctionHouse,
      auctioneerAuthority
    } = operation.input;
    const builder = updateAuctionHouseBuilder(metaplex, operation.input, scope);
    if (builder.isEmpty()) {
      throw new NoInstructionsToSendError(Key);
    }
    const output = await builder.sendAndConfirm(metaplex, scope.confirmOptions);
    const currentAuctioneerAuthority = auctionHouse.hasAuctioneer ? auctionHouse.auctioneer.authority : undefined;
    const updatedAuctionHouse = await metaplex.auctionHouse().findByAddress({
      address: auctionHouse.address,
      auctioneerAuthority: auctioneerAuthority ?? currentAuctioneerAuthority
    }, scope);
    return {
      ...output,
      auctionHouse: updatedAuctionHouse
    };
  }
};

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

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

/**
 * Updates an existing Auction House.
 *
 * ```ts
 * const transactionBuilder = metaplex
 *   .auctionHouse()
 *   .builders()
 *   .updateAuctionHouse({ auctionHouse, canChangeSalePrice: true })
 * ```
 *
 * @group Transaction Builders
 * @category Constructors
 */
const updateAuctionHouseBuilder = (metaplex, params, options = {}) => {
  const {
    programs,
    payer = metaplex.rpc().getDefaultFeePayer()
  } = options;
  const authority = params.authority ?? metaplex.identity();
  const {
    auctionHouse
  } = params;
  let treasuryWithdrawalDestinationOwner;
  let treasuryWithdrawalDestination;
  if (auctionHouse.isNative) {
    treasuryWithdrawalDestinationOwner = params.treasuryWithdrawalDestinationOwner ?? auctionHouse.treasuryWithdrawalDestinationAddress;
    treasuryWithdrawalDestination = treasuryWithdrawalDestinationOwner;
  } else if (params.treasuryWithdrawalDestinationOwner) {
    treasuryWithdrawalDestinationOwner = params.treasuryWithdrawalDestinationOwner;
    treasuryWithdrawalDestination = metaplex.tokens().pdas().associatedTokenAccount({
      mint: auctionHouse.treasuryMint.address,
      owner: treasuryWithdrawalDestinationOwner,
      programs
    });
  } else {
    throw new TreasuryDestinationOwnerRequiredError();
  }
  const originalData = {
    authority: auctionHouse.authorityAddress,
    feeWithdrawalDestination: auctionHouse.feeWithdrawalDestinationAddress,
    treasuryWithdrawalDestination: auctionHouse.treasuryWithdrawalDestinationAddress,
    sellerFeeBasisPoints: auctionHouse.sellerFeeBasisPoints,
    requiresSignOff: auctionHouse.requiresSignOff,
    canChangeSalePrice: auctionHouse.canChangeSalePrice
  };
  const updatedData = {
    authority: params.newAuthority ?? originalData.authority,
    feeWithdrawalDestination: params.feeWithdrawalDestination ?? originalData.feeWithdrawalDestination,
    treasuryWithdrawalDestination,
    sellerFeeBasisPoints: params.sellerFeeBasisPoints ?? originalData.sellerFeeBasisPoints,
    requiresSignOff: params.requiresSignOff ?? originalData.requiresSignOff,
    canChangeSalePrice: params.canChangeSalePrice ?? originalData.canChangeSalePrice
  };
  const shouldSendUpdateInstruction = !isEqual(originalData, updatedData);
  const shouldAddAuctioneerAuthority = !auctionHouse.hasAuctioneer && !!params.auctioneerAuthority;
  const shouldUpdateAuctioneerAuthority = auctionHouse.hasAuctioneer && !!params.auctioneerAuthority && !params.auctioneerAuthority.equals(auctionHouse.auctioneer.authority);
  const shouldUpdateAuctioneerScopes = auctionHouse.hasAuctioneer && !!params.auctioneerScopes && !isEqual(params.auctioneerScopes.sort(), auctionHouse.auctioneer.scopes.sort());
  const shouldDelegateAuctioneer = shouldAddAuctioneerAuthority || shouldUpdateAuctioneerAuthority;
  return TransactionBuilder.make().setFeePayer(payer)

  // Update the Auction House data.
  .when(shouldSendUpdateInstruction, builder => builder.add({
    instruction: createUpdateAuctionHouseInstruction({
      treasuryMint: auctionHouse.treasuryMint.address,
      payer: payer.publicKey,
      authority: authority.publicKey,
      newAuthority: updatedData.authority,
      feeWithdrawalDestination: updatedData.feeWithdrawalDestination,
      treasuryWithdrawalDestination,
      treasuryWithdrawalDestinationOwner,
      auctionHouse: auctionHouse.address
    }, {
      sellerFeeBasisPoints: params.sellerFeeBasisPoints ?? null,
      requiresSignOff: params.requiresSignOff ?? null,
      canChangeSalePrice: params.canChangeSalePrice ?? null
    }),
    signers: [payer, authority],
    key: params.instructionKey ?? 'updateAuctionHouse'
  }))

  // Attach or update a new Auctioneer instance to the Auction House.
  .when(shouldDelegateAuctioneer, builder => {
    const auctioneerAuthority = params.auctioneerAuthority;
    const defaultScopes = auctionHouse.hasAuctioneer ? auctionHouse.auctioneer.scopes : AUCTIONEER_ALL_SCOPES;
    return builder.add({
      instruction: createDelegateAuctioneerInstruction({
        auctionHouse: auctionHouse.address,
        authority: authority.publicKey,
        auctioneerAuthority,
        ahAuctioneerPda: metaplex.auctionHouse().pdas().auctioneer({
          auctionHouse: auctionHouse.address,
          auctioneerAuthority,
          programs
        })
      }, {
        scopes: params.auctioneerScopes ?? defaultScopes
      }),
      signers: [authority],
      key: params.delegateAuctioneerInstructionKey ?? 'delegateAuctioneer'
    });
  })

  // Update the Auctioneer scopes of the Auction House.
  .when(shouldUpdateAuctioneerScopes, builder => {
    assertAuctioneerAuctionHouse(auctionHouse);
    const auctioneerAuthority = params.auctioneerAuthority ?? auctionHouse.auctioneer.authority;
    return builder.add({
      instruction: createUpdateAuctioneerInstruction({
        auctionHouse: auctionHouse.address,
        authority: authority.publicKey,
        auctioneerAuthority,
        ahAuctioneerPda: metaplex.auctionHouse().pdas().auctioneer({
          auctionHouse: auctionHouse.address,
          auctioneerAuthority,
          programs
        })
      }, {
        scopes: params.auctioneerScopes ?? auctionHouse.auctioneer.scopes
      }),
      signers: [authority],
      key: params.updateAuctioneerInstructionKey ?? 'updateAuctioneer'
    });
  });
};

export { updateAuctionHouseBuilder, updateAuctionHouseOperation, updateAuctionHouseOperationHandler };
//# sourceMappingURL=updateAuctionHouse.mjs.map
