import type { BN } from "@project-serum/anchor";
import type { Wallet } from "@saberhq/solana-contrib";
import type { Connection, PublicKey, Transaction } from "@solana/web3.js";
import { Keypair } from "@solana/web3.js";

import { withFindOrInitAssociatedTokenAccount } from "../../utils/withFindOrInitAssociatedTokenAccount";
import { getMinter } from "./accounts";
import type { MetadataKind } from "./constants";
import {
  closeMinterInstruction,
  initMinterInstruction,
  mintInstruction,
  setAsRedeemedInstruction,
  updateMinterInstruction,
} from "./instruction";
import { findMinterId, findRedeemedMintsId } from "./pda";
import { withRemainingAccountsForMint } from "./utils";

export const withInitMinter = async (
  transaction: Transaction,
  connection: Connection,
  wallet: Wallet,
  params: {
    projectId: PublicKey;
    mintId: PublicKey;

    authorizedCreators?: PublicKey[];
    metadataKind?: MetadataKind;
    maxMint?: number;
    minSetSize?: number;
    start?: BN;
    end?: BN;
  }
): Promise<[Transaction, PublicKey, PublicKey]> => {
  const { publicKey: identifierId } = Keypair.generate();

  const minterId = findMinterId(identifierId);
  const redeemedMintsId = findRedeemedMintsId(minterId);

  transaction.add(
    await initMinterInstruction(connection, wallet, {
      ...params,
      identifierId,
      minterId,
      redeemedMintsId,
    })
  );
  return [transaction, minterId, identifierId];
};

export const withUpdateMinter = async (
  transaction: Transaction,
  connection: Connection,
  wallet: Wallet,
  params: {
    minterId: PublicKey;

    authorizedCreators?: PublicKey[];
    metadataKind?: MetadataKind;
    maxMint?: number;
    minSetSize?: number;
    start?: BN;
    end?: BN;
  }
): Promise<Transaction> => {
  const minterData = await getMinter(connection, params.minterId);

  transaction.add(
    await updateMinterInstruction(connection, wallet, {
      projectId: minterData.parsed.project,
      ...params,
    })
  );
  return transaction;
};

export const withCloseMinter = async (
  transaction: Transaction,
  connection: Connection,
  wallet: Wallet,
  params: {
    minterId: PublicKey;
  }
): Promise<Transaction> => {
  const redeemedMintsId = findRedeemedMintsId(params.minterId);
  const minterData = await getMinter(connection, params.minterId);

  transaction.add(
    await closeMinterInstruction(connection, wallet, {
      minterId: params.minterId,
      redeemedMintsId,
      projectId: minterData.parsed.project,
      mintId: minterData.parsed.mint,
    })
  );
  return transaction;
};

export const withMint = async (
  transaction: Transaction,
  connection: Connection,
  wallet: Wallet,
  params: {
    minterId: PublicKey;
    setMints: PublicKey[];
  }
): Promise<Transaction> => {
  const redeemedMintsId = findRedeemedMintsId(params.minterId);
  const minterData = await getMinter(connection, params.minterId);
  const [userTokenAccountId, remainingAccounts] = await Promise.all([
    withFindOrInitAssociatedTokenAccount(
      transaction,
      connection,
      minterData.parsed.mint,
      wallet.publicKey,
      wallet.publicKey
    ),
    withRemainingAccountsForMint(transaction, connection, wallet, {
      mints: params.setMints,
    }),
  ]);

  transaction.add(
    await mintInstruction(connection, wallet, {
      minterId: params.minterId,
      userTokenAccountId,
      redeemedMintsId,
      mintId: minterData.parsed.mint,
      remainingAccounts,
    })
  );
  return transaction;
};

export const withSetAsRedeemed = async (
  transaction: Transaction,
  connection: Connection,
  wallet: Wallet,
  params: {
    minterId: PublicKey;
    mints: PublicKey[];
  }
): Promise<Transaction> => {
  const redeemedMintsId = findRedeemedMintsId(params.minterId);
  const minterData = await getMinter(connection, params.minterId);

  transaction.add(
    await setAsRedeemedInstruction(connection, wallet, {
      minterId: params.minterId,
      projectId: minterData.parsed.project,
      redeemedMintsId,
      mints: params.mints,
    })
  );
  return transaction;
};
