/* eslint-disable @typescript-eslint/no-unsafe-assignment */
import type { BN } from "@project-serum/anchor";
import { AnchorProvider, Program } from "@project-serum/anchor";
import type { Wallet } from "@saberhq/solana-contrib";
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
import type {
  AccountMeta,
  Connection,
  PublicKey,
  Transaction,
} from "@solana/web3.js";
import { SystemProgram } from "@solana/web3.js";

import type { MetadataKind, MINTER_PROGRAM } from "./constants";
import { MINTER_ADDRESS, MINTER_IDL } from "./constants";

const getProgram = (connection: Connection, wallet: Wallet) => {
  const provider = new AnchorProvider(connection, wallet, {});
  const program = new Program<MINTER_PROGRAM>(
    MINTER_IDL,
    MINTER_ADDRESS,
    provider
  );
  return program;
};

export const initMinterInstruction = (
  connection: Connection,
  wallet: Wallet,
  params: {
    identifierId: PublicKey;
    minterId: PublicKey;
    redeemedMintsId: PublicKey;
    projectId: PublicKey;
    mintId: PublicKey;

    authorizedCreators?: PublicKey[];
    metadataKind?: MetadataKind;
    maxMint?: number;
    minSetSize?: number;
    start?: BN;
    end?: BN;
  }
): Promise<Transaction> => {
  const program = getProgram(connection, wallet);

  return program.methods
    .initMinter({
      identifier: params.identifierId,
      authorizedCreators: params.authorizedCreators || [],
      metadataKind: params.metadataKind || null,
      maxMint: params.maxMint || null,
      minSetSize: params.minSetSize || null,
      start: params.start || null,
      end: params.end || null,
    })
    .accounts({
      minter: params.minterId,
      redeemedMints: params.redeemedMintsId,
      project: params.projectId,
      mint: params.mintId,
      payer: wallet.publicKey,
      systemProgram: SystemProgram.programId,
      tokenProgram: TOKEN_PROGRAM_ID,
    })
    .transaction();
};

export const updateMinterInstruction = (
  connection: Connection,
  wallet: Wallet,
  params: {
    minterId: PublicKey;
    projectId: PublicKey;

    authorizedCreators?: PublicKey[];
    metadataKind?: MetadataKind;
    maxMint?: number;
    minSetSize?: number;
    start?: BN;
    end?: BN;
  }
): Promise<Transaction> => {
  const program = getProgram(connection, wallet);
  return program.methods
    .updateMinter({
      authorizedCreators: params.authorizedCreators || [],
      metadataKind: params.metadataKind || null,
      maxMint: params.maxMint || null,
      minSetSize: params.minSetSize || null,
      start: params.start || null,
      end: params.end || null,
    })
    .accounts({
      minter: params.minterId,
      project: params.projectId,
      authority: wallet.publicKey,
    })
    .transaction();
};

export const closeMinterInstruction = (
  connection: Connection,
  wallet: Wallet,
  params: {
    minterId: PublicKey;
    redeemedMintsId: PublicKey;
    projectId: PublicKey;
    mintId: PublicKey;
  }
): Promise<Transaction> => {
  const program = getProgram(connection, wallet);
  return program.methods
    .closeMinter()
    .accounts({
      minter: params.minterId,
      redeemedMints: params.redeemedMintsId,
      project: params.projectId,
      mint: params.mintId,
      authority: wallet.publicKey,
      tokenProgram: TOKEN_PROGRAM_ID,
    })
    .transaction();
};

export const mintInstruction = (
  connection: Connection,
  wallet: Wallet,
  params: {
    minterId: PublicKey;
    redeemedMintsId: PublicKey;
    userTokenAccountId: PublicKey;
    mintId: PublicKey;
    remainingAccounts: AccountMeta[];
  }
): Promise<Transaction> => {
  const program = getProgram(connection, wallet);
  return program.methods
    .mint()
    .accounts({
      minter: params.minterId,
      userTokenAccount: params.userTokenAccountId,
      redeemedMints: params.redeemedMintsId,
      mint: params.mintId,
      user: wallet.publicKey,
      tokenProgram: TOKEN_PROGRAM_ID,
    })
    .remainingAccounts(params.remainingAccounts)
    .transaction();
};

export const setAsRedeemedInstruction = (
  connection: Connection,
  wallet: Wallet,
  params: {
    minterId: PublicKey;
    projectId: PublicKey;
    redeemedMintsId: PublicKey;
    mints: PublicKey[];
  }
): Promise<Transaction> => {
  const program = getProgram(connection, wallet);
  return program.methods
    .setAsRedeemed(params.mints)
    .accounts({
      minter: params.minterId,
      redeemedMints: params.redeemedMintsId,
      project: params.projectId,
      authority: wallet.publicKey,
      tokenProgram: TOKEN_PROGRAM_ID,
      systemProgram: SystemProgram.programId,
    })
    .transaction();
};
