/**
 * This code was AUTOGENERATED using the codama library.
 * Please DO NOT EDIT THIS FILE, instead use visitors
 * to add features, then rerun codama to update it.
 *
 * @see https://github.com/codama-idl/codama
 */

import {
  addDecoderSizePrefix,
  addEncoderSizePrefix,
  combineCodec,
  fixDecoderSize,
  fixEncoderSize,
  getAddressEncoder,
  getArrayDecoder,
  getArrayEncoder,
  getBytesDecoder,
  getBytesEncoder,
  getOptionDecoder,
  getOptionEncoder,
  getProgramDerivedAddress,
  getStructDecoder,
  getStructEncoder,
  getU32Decoder,
  getU32Encoder,
  getUtf8Decoder,
  getUtf8Encoder,
  transformEncoder,
  type AccountMeta,
  type AccountSignerMeta,
  type Address,
  type Codec,
  type Decoder,
  type Encoder,
  type Instruction,
  type InstructionWithAccounts,
  type InstructionWithData,
  type Option,
  type OptionOrNullable,
  type ReadonlyAccount,
  type ReadonlySignerAccount,
  type ReadonlyUint8Array,
  type TransactionSigner,
  type WritableAccount,
  type WritableSignerAccount,
} from '@solana/kit';
import { WEN_TRANSFER_GUARD_PROGRAM_ADDRESS } from '../programs';
import {
  expectAddress,
  getAccountMetaFactory,
  type ResolvedAccount,
} from '../shared';
import {
  getCpiRuleDecoder,
  getCpiRuleEncoder,
  getMetadataAdditionalFieldRuleDecoder,
  getMetadataAdditionalFieldRuleEncoder,
  getTransferAmountRuleDecoder,
  getTransferAmountRuleEncoder,
  type CpiRule,
  type CpiRuleArgs,
  type MetadataAdditionalFieldRule,
  type MetadataAdditionalFieldRuleArgs,
  type TransferAmountRule,
  type TransferAmountRuleArgs,
} from '../types';

export const CREATE_GUARD_DISCRIMINATOR = new Uint8Array([
  251, 254, 17, 198, 219, 218, 154, 99,
]);

export function getCreateGuardDiscriminatorBytes() {
  return fixEncoderSize(getBytesEncoder(), 8).encode(
    CREATE_GUARD_DISCRIMINATOR
  );
}

export type CreateGuardInstruction<
  TProgram extends string = typeof WEN_TRANSFER_GUARD_PROGRAM_ADDRESS,
  TAccountGuard extends string | AccountMeta<string> = string,
  TAccountMint extends string | AccountMeta<string> = string,
  TAccountMintTokenAccount extends string | AccountMeta<string> = string,
  TAccountGuardAuthority extends string | AccountMeta<string> = string,
  TAccountPayer extends string | AccountMeta<string> = string,
  TAccountAssociatedTokenProgram extends
    | string
    | AccountMeta<string> = 'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL',
  TAccountTokenProgram extends
    | string
    | AccountMeta<string> = 'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb',
  TAccountSystemProgram extends
    | string
    | AccountMeta<string> = '11111111111111111111111111111111',
  TRemainingAccounts extends readonly AccountMeta<string>[] = [],
> = Instruction<TProgram> &
  InstructionWithData<ReadonlyUint8Array> &
  InstructionWithAccounts<
    [
      TAccountGuard extends string
        ? WritableAccount<TAccountGuard>
        : TAccountGuard,
      TAccountMint extends string
        ? WritableSignerAccount<TAccountMint> & AccountSignerMeta<TAccountMint>
        : TAccountMint,
      TAccountMintTokenAccount extends string
        ? WritableAccount<TAccountMintTokenAccount>
        : TAccountMintTokenAccount,
      TAccountGuardAuthority extends string
        ? ReadonlySignerAccount<TAccountGuardAuthority> &
            AccountSignerMeta<TAccountGuardAuthority>
        : TAccountGuardAuthority,
      TAccountPayer extends string
        ? WritableSignerAccount<TAccountPayer> &
            AccountSignerMeta<TAccountPayer>
        : TAccountPayer,
      TAccountAssociatedTokenProgram extends string
        ? ReadonlyAccount<TAccountAssociatedTokenProgram>
        : TAccountAssociatedTokenProgram,
      TAccountTokenProgram extends string
        ? ReadonlyAccount<TAccountTokenProgram>
        : TAccountTokenProgram,
      TAccountSystemProgram extends string
        ? ReadonlyAccount<TAccountSystemProgram>
        : TAccountSystemProgram,
      ...TRemainingAccounts,
    ]
  >;

export type CreateGuardInstructionData = {
  discriminator: ReadonlyUint8Array;
  name: string;
  symbol: string;
  uri: string;
  cpiRule: Option<CpiRule>;
  transferAmountRule: Option<TransferAmountRule>;
  additionalFieldsRule: Array<MetadataAdditionalFieldRule>;
};

export type CreateGuardInstructionDataArgs = {
  name: string;
  symbol: string;
  uri: string;
  cpiRule: OptionOrNullable<CpiRuleArgs>;
  transferAmountRule: OptionOrNullable<TransferAmountRuleArgs>;
  additionalFieldsRule: Array<MetadataAdditionalFieldRuleArgs>;
};

export function getCreateGuardInstructionDataEncoder(): Encoder<CreateGuardInstructionDataArgs> {
  return transformEncoder(
    getStructEncoder([
      ['discriminator', fixEncoderSize(getBytesEncoder(), 8)],
      ['name', addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder())],
      ['symbol', addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder())],
      ['uri', addEncoderSizePrefix(getUtf8Encoder(), getU32Encoder())],
      ['cpiRule', getOptionEncoder(getCpiRuleEncoder())],
      ['transferAmountRule', getOptionEncoder(getTransferAmountRuleEncoder())],
      [
        'additionalFieldsRule',
        getArrayEncoder(getMetadataAdditionalFieldRuleEncoder()),
      ],
    ]),
    (value) => ({ ...value, discriminator: CREATE_GUARD_DISCRIMINATOR })
  );
}

export function getCreateGuardInstructionDataDecoder(): Decoder<CreateGuardInstructionData> {
  return getStructDecoder([
    ['discriminator', fixDecoderSize(getBytesDecoder(), 8)],
    ['name', addDecoderSizePrefix(getUtf8Decoder(), getU32Decoder())],
    ['symbol', addDecoderSizePrefix(getUtf8Decoder(), getU32Decoder())],
    ['uri', addDecoderSizePrefix(getUtf8Decoder(), getU32Decoder())],
    ['cpiRule', getOptionDecoder(getCpiRuleDecoder())],
    ['transferAmountRule', getOptionDecoder(getTransferAmountRuleDecoder())],
    [
      'additionalFieldsRule',
      getArrayDecoder(getMetadataAdditionalFieldRuleDecoder()),
    ],
  ]);
}

export function getCreateGuardInstructionDataCodec(): Codec<
  CreateGuardInstructionDataArgs,
  CreateGuardInstructionData
> {
  return combineCodec(
    getCreateGuardInstructionDataEncoder(),
    getCreateGuardInstructionDataDecoder()
  );
}

export type CreateGuardAsyncInput<
  TAccountGuard extends string = string,
  TAccountMint extends string = string,
  TAccountMintTokenAccount extends string = string,
  TAccountGuardAuthority extends string = string,
  TAccountPayer extends string = string,
  TAccountAssociatedTokenProgram extends string = string,
  TAccountTokenProgram extends string = string,
  TAccountSystemProgram extends string = string,
> = {
  guard?: Address<TAccountGuard>;
  mint: TransactionSigner<TAccountMint>;
  mintTokenAccount?: Address<TAccountMintTokenAccount>;
  guardAuthority: TransactionSigner<TAccountGuardAuthority>;
  payer: TransactionSigner<TAccountPayer>;
  associatedTokenProgram?: Address<TAccountAssociatedTokenProgram>;
  tokenProgram?: Address<TAccountTokenProgram>;
  systemProgram?: Address<TAccountSystemProgram>;
  name: CreateGuardInstructionDataArgs['name'];
  symbol: CreateGuardInstructionDataArgs['symbol'];
  uri: CreateGuardInstructionDataArgs['uri'];
  cpiRule: CreateGuardInstructionDataArgs['cpiRule'];
  transferAmountRule: CreateGuardInstructionDataArgs['transferAmountRule'];
  additionalFieldsRule: CreateGuardInstructionDataArgs['additionalFieldsRule'];
};

export async function getCreateGuardInstructionAsync<
  TAccountGuard extends string,
  TAccountMint extends string,
  TAccountMintTokenAccount extends string,
  TAccountGuardAuthority extends string,
  TAccountPayer extends string,
  TAccountAssociatedTokenProgram extends string,
  TAccountTokenProgram extends string,
  TAccountSystemProgram extends string,
  TProgramAddress extends Address = typeof WEN_TRANSFER_GUARD_PROGRAM_ADDRESS,
>(
  input: CreateGuardAsyncInput<
    TAccountGuard,
    TAccountMint,
    TAccountMintTokenAccount,
    TAccountGuardAuthority,
    TAccountPayer,
    TAccountAssociatedTokenProgram,
    TAccountTokenProgram,
    TAccountSystemProgram
  >,
  config?: { programAddress?: TProgramAddress }
): Promise<
  CreateGuardInstruction<
    TProgramAddress,
    TAccountGuard,
    TAccountMint,
    TAccountMintTokenAccount,
    TAccountGuardAuthority,
    TAccountPayer,
    TAccountAssociatedTokenProgram,
    TAccountTokenProgram,
    TAccountSystemProgram
  >
> {
  // Program address.
  const programAddress =
    config?.programAddress ?? WEN_TRANSFER_GUARD_PROGRAM_ADDRESS;

  // Original accounts.
  const originalAccounts = {
    guard: { value: input.guard ?? null, isWritable: true },
    mint: { value: input.mint ?? null, isWritable: true },
    mintTokenAccount: {
      value: input.mintTokenAccount ?? null,
      isWritable: true,
    },
    guardAuthority: { value: input.guardAuthority ?? null, isWritable: false },
    payer: { value: input.payer ?? null, isWritable: true },
    associatedTokenProgram: {
      value: input.associatedTokenProgram ?? null,
      isWritable: false,
    },
    tokenProgram: { value: input.tokenProgram ?? null, isWritable: false },
    systemProgram: { value: input.systemProgram ?? null, isWritable: false },
  };
  const accounts = originalAccounts as Record<
    keyof typeof originalAccounts,
    ResolvedAccount
  >;

  // Original args.
  const args = { ...input };

  // Resolve default values.
  if (!accounts.guard.value) {
    accounts.guard.value = await getProgramDerivedAddress({
      programAddress,
      seeds: [
        getBytesEncoder().encode(
          new Uint8Array([
            119, 101, 110, 95, 116, 111, 107, 101, 110, 95, 116, 114, 97, 110,
            115, 102, 101, 114, 95, 103, 117, 97, 114, 100,
          ])
        ),
        getBytesEncoder().encode(
          new Uint8Array([103, 117, 97, 114, 100, 95, 118, 49])
        ),
        getAddressEncoder().encode(expectAddress(accounts.mint.value)),
      ],
    });
  }
  if (!accounts.tokenProgram.value) {
    accounts.tokenProgram.value =
      'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb' as Address<'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'>;
  }
  if (!accounts.mintTokenAccount.value) {
    accounts.mintTokenAccount.value = await getProgramDerivedAddress({
      programAddress:
        'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' as Address<'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'>,
      seeds: [
        getAddressEncoder().encode(
          expectAddress(accounts.guardAuthority.value)
        ),
        getAddressEncoder().encode(expectAddress(accounts.tokenProgram.value)),
        getAddressEncoder().encode(expectAddress(accounts.mint.value)),
      ],
    });
  }
  if (!accounts.associatedTokenProgram.value) {
    accounts.associatedTokenProgram.value =
      'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' as Address<'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'>;
  }
  if (!accounts.systemProgram.value) {
    accounts.systemProgram.value =
      '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>;
  }

  const getAccountMeta = getAccountMetaFactory(programAddress, 'programId');
  const instruction = {
    accounts: [
      getAccountMeta(accounts.guard),
      getAccountMeta(accounts.mint),
      getAccountMeta(accounts.mintTokenAccount),
      getAccountMeta(accounts.guardAuthority),
      getAccountMeta(accounts.payer),
      getAccountMeta(accounts.associatedTokenProgram),
      getAccountMeta(accounts.tokenProgram),
      getAccountMeta(accounts.systemProgram),
    ],
    programAddress,
    data: getCreateGuardInstructionDataEncoder().encode(
      args as CreateGuardInstructionDataArgs
    ),
  } as CreateGuardInstruction<
    TProgramAddress,
    TAccountGuard,
    TAccountMint,
    TAccountMintTokenAccount,
    TAccountGuardAuthority,
    TAccountPayer,
    TAccountAssociatedTokenProgram,
    TAccountTokenProgram,
    TAccountSystemProgram
  >;

  return instruction;
}

export type CreateGuardInput<
  TAccountGuard extends string = string,
  TAccountMint extends string = string,
  TAccountMintTokenAccount extends string = string,
  TAccountGuardAuthority extends string = string,
  TAccountPayer extends string = string,
  TAccountAssociatedTokenProgram extends string = string,
  TAccountTokenProgram extends string = string,
  TAccountSystemProgram extends string = string,
> = {
  guard: Address<TAccountGuard>;
  mint: TransactionSigner<TAccountMint>;
  mintTokenAccount: Address<TAccountMintTokenAccount>;
  guardAuthority: TransactionSigner<TAccountGuardAuthority>;
  payer: TransactionSigner<TAccountPayer>;
  associatedTokenProgram?: Address<TAccountAssociatedTokenProgram>;
  tokenProgram?: Address<TAccountTokenProgram>;
  systemProgram?: Address<TAccountSystemProgram>;
  name: CreateGuardInstructionDataArgs['name'];
  symbol: CreateGuardInstructionDataArgs['symbol'];
  uri: CreateGuardInstructionDataArgs['uri'];
  cpiRule: CreateGuardInstructionDataArgs['cpiRule'];
  transferAmountRule: CreateGuardInstructionDataArgs['transferAmountRule'];
  additionalFieldsRule: CreateGuardInstructionDataArgs['additionalFieldsRule'];
};

export function getCreateGuardInstruction<
  TAccountGuard extends string,
  TAccountMint extends string,
  TAccountMintTokenAccount extends string,
  TAccountGuardAuthority extends string,
  TAccountPayer extends string,
  TAccountAssociatedTokenProgram extends string,
  TAccountTokenProgram extends string,
  TAccountSystemProgram extends string,
  TProgramAddress extends Address = typeof WEN_TRANSFER_GUARD_PROGRAM_ADDRESS,
>(
  input: CreateGuardInput<
    TAccountGuard,
    TAccountMint,
    TAccountMintTokenAccount,
    TAccountGuardAuthority,
    TAccountPayer,
    TAccountAssociatedTokenProgram,
    TAccountTokenProgram,
    TAccountSystemProgram
  >,
  config?: { programAddress?: TProgramAddress }
): CreateGuardInstruction<
  TProgramAddress,
  TAccountGuard,
  TAccountMint,
  TAccountMintTokenAccount,
  TAccountGuardAuthority,
  TAccountPayer,
  TAccountAssociatedTokenProgram,
  TAccountTokenProgram,
  TAccountSystemProgram
> {
  // Program address.
  const programAddress =
    config?.programAddress ?? WEN_TRANSFER_GUARD_PROGRAM_ADDRESS;

  // Original accounts.
  const originalAccounts = {
    guard: { value: input.guard ?? null, isWritable: true },
    mint: { value: input.mint ?? null, isWritable: true },
    mintTokenAccount: {
      value: input.mintTokenAccount ?? null,
      isWritable: true,
    },
    guardAuthority: { value: input.guardAuthority ?? null, isWritable: false },
    payer: { value: input.payer ?? null, isWritable: true },
    associatedTokenProgram: {
      value: input.associatedTokenProgram ?? null,
      isWritable: false,
    },
    tokenProgram: { value: input.tokenProgram ?? null, isWritable: false },
    systemProgram: { value: input.systemProgram ?? null, isWritable: false },
  };
  const accounts = originalAccounts as Record<
    keyof typeof originalAccounts,
    ResolvedAccount
  >;

  // Original args.
  const args = { ...input };

  // Resolve default values.
  if (!accounts.tokenProgram.value) {
    accounts.tokenProgram.value =
      'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb' as Address<'TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb'>;
  }
  if (!accounts.associatedTokenProgram.value) {
    accounts.associatedTokenProgram.value =
      'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL' as Address<'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'>;
  }
  if (!accounts.systemProgram.value) {
    accounts.systemProgram.value =
      '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>;
  }

  const getAccountMeta = getAccountMetaFactory(programAddress, 'programId');
  const instruction = {
    accounts: [
      getAccountMeta(accounts.guard),
      getAccountMeta(accounts.mint),
      getAccountMeta(accounts.mintTokenAccount),
      getAccountMeta(accounts.guardAuthority),
      getAccountMeta(accounts.payer),
      getAccountMeta(accounts.associatedTokenProgram),
      getAccountMeta(accounts.tokenProgram),
      getAccountMeta(accounts.systemProgram),
    ],
    programAddress,
    data: getCreateGuardInstructionDataEncoder().encode(
      args as CreateGuardInstructionDataArgs
    ),
  } as CreateGuardInstruction<
    TProgramAddress,
    TAccountGuard,
    TAccountMint,
    TAccountMintTokenAccount,
    TAccountGuardAuthority,
    TAccountPayer,
    TAccountAssociatedTokenProgram,
    TAccountTokenProgram,
    TAccountSystemProgram
  >;

  return instruction;
}

export type ParsedCreateGuardInstruction<
  TProgram extends string = typeof WEN_TRANSFER_GUARD_PROGRAM_ADDRESS,
  TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[],
> = {
  programAddress: Address<TProgram>;
  accounts: {
    guard: TAccountMetas[0];
    mint: TAccountMetas[1];
    mintTokenAccount: TAccountMetas[2];
    guardAuthority: TAccountMetas[3];
    payer: TAccountMetas[4];
    associatedTokenProgram: TAccountMetas[5];
    tokenProgram: TAccountMetas[6];
    systemProgram: TAccountMetas[7];
  };
  data: CreateGuardInstructionData;
};

export function parseCreateGuardInstruction<
  TProgram extends string,
  TAccountMetas extends readonly AccountMeta[],
>(
  instruction: Instruction<TProgram> &
    InstructionWithAccounts<TAccountMetas> &
    InstructionWithData<ReadonlyUint8Array>
): ParsedCreateGuardInstruction<TProgram, TAccountMetas> {
  if (instruction.accounts.length < 8) {
    // TODO: Coded error.
    throw new Error('Not enough accounts');
  }
  let accountIndex = 0;
  const getNextAccount = () => {
    const accountMeta = instruction.accounts![accountIndex]!;
    accountIndex += 1;
    return accountMeta;
  };
  return {
    programAddress: instruction.programAddress,
    accounts: {
      guard: getNextAccount(),
      mint: getNextAccount(),
      mintTokenAccount: getNextAccount(),
      guardAuthority: getNextAccount(),
      payer: getNextAccount(),
      associatedTokenProgram: getNextAccount(),
      tokenProgram: getNextAccount(),
      systemProgram: getNextAccount(),
    },
    data: getCreateGuardInstructionDataDecoder().decode(instruction.data),
  };
}
