/**
 * 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 {
  combineCodec,
  fixDecoderSize,
  fixEncoderSize,
  getAddressEncoder,
  getArrayDecoder,
  getArrayEncoder,
  getBytesDecoder,
  getBytesEncoder,
  getOptionDecoder,
  getOptionEncoder,
  getProgramDerivedAddress,
  getStructDecoder,
  getStructEncoder,
  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,
} 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 UPDATE_GUARD_DISCRIMINATOR = new Uint8Array([
  51, 38, 175, 180, 25, 249, 39, 24,
]);

export function getUpdateGuardDiscriminatorBytes() {
  return fixEncoderSize(getBytesEncoder(), 8).encode(
    UPDATE_GUARD_DISCRIMINATOR
  );
}

export type UpdateGuardInstruction<
  TProgram extends string = typeof WEN_TRANSFER_GUARD_PROGRAM_ADDRESS,
  TAccountGuard extends string | AccountMeta<string> = string,
  TAccountMint extends string | AccountMeta<string> = string,
  TAccountTokenAccount extends string | AccountMeta<string> = string,
  TAccountGuardAuthority extends string | AccountMeta<string> = string,
  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
        ? ReadonlyAccount<TAccountMint>
        : TAccountMint,
      TAccountTokenAccount extends string
        ? ReadonlyAccount<TAccountTokenAccount>
        : TAccountTokenAccount,
      TAccountGuardAuthority extends string
        ? ReadonlySignerAccount<TAccountGuardAuthority> &
            AccountSignerMeta<TAccountGuardAuthority>
        : TAccountGuardAuthority,
      TAccountTokenProgram extends string
        ? ReadonlyAccount<TAccountTokenProgram>
        : TAccountTokenProgram,
      TAccountSystemProgram extends string
        ? ReadonlyAccount<TAccountSystemProgram>
        : TAccountSystemProgram,
      ...TRemainingAccounts,
    ]
  >;

export type UpdateGuardInstructionData = {
  discriminator: ReadonlyUint8Array;
  cpiRule: Option<CpiRule>;
  transferAmountRule: Option<TransferAmountRule>;
  additionalFieldsRule: Array<MetadataAdditionalFieldRule>;
};

export type UpdateGuardInstructionDataArgs = {
  cpiRule: OptionOrNullable<CpiRuleArgs>;
  transferAmountRule: OptionOrNullable<TransferAmountRuleArgs>;
  additionalFieldsRule: Array<MetadataAdditionalFieldRuleArgs>;
};

export function getUpdateGuardInstructionDataEncoder(): Encoder<UpdateGuardInstructionDataArgs> {
  return transformEncoder(
    getStructEncoder([
      ['discriminator', fixEncoderSize(getBytesEncoder(), 8)],
      ['cpiRule', getOptionEncoder(getCpiRuleEncoder())],
      ['transferAmountRule', getOptionEncoder(getTransferAmountRuleEncoder())],
      [
        'additionalFieldsRule',
        getArrayEncoder(getMetadataAdditionalFieldRuleEncoder()),
      ],
    ]),
    (value) => ({ ...value, discriminator: UPDATE_GUARD_DISCRIMINATOR })
  );
}

export function getUpdateGuardInstructionDataDecoder(): Decoder<UpdateGuardInstructionData> {
  return getStructDecoder([
    ['discriminator', fixDecoderSize(getBytesDecoder(), 8)],
    ['cpiRule', getOptionDecoder(getCpiRuleDecoder())],
    ['transferAmountRule', getOptionDecoder(getTransferAmountRuleDecoder())],
    [
      'additionalFieldsRule',
      getArrayDecoder(getMetadataAdditionalFieldRuleDecoder()),
    ],
  ]);
}

export function getUpdateGuardInstructionDataCodec(): Codec<
  UpdateGuardInstructionDataArgs,
  UpdateGuardInstructionData
> {
  return combineCodec(
    getUpdateGuardInstructionDataEncoder(),
    getUpdateGuardInstructionDataDecoder()
  );
}

export type UpdateGuardAsyncInput<
  TAccountGuard extends string = string,
  TAccountMint extends string = string,
  TAccountTokenAccount extends string = string,
  TAccountGuardAuthority extends string = string,
  TAccountTokenProgram extends string = string,
  TAccountSystemProgram extends string = string,
> = {
  guard?: Address<TAccountGuard>;
  mint: Address<TAccountMint>;
  tokenAccount?: Address<TAccountTokenAccount>;
  guardAuthority: TransactionSigner<TAccountGuardAuthority>;
  tokenProgram?: Address<TAccountTokenProgram>;
  systemProgram?: Address<TAccountSystemProgram>;
  cpiRule: UpdateGuardInstructionDataArgs['cpiRule'];
  transferAmountRule: UpdateGuardInstructionDataArgs['transferAmountRule'];
  additionalFieldsRule: UpdateGuardInstructionDataArgs['additionalFieldsRule'];
};

export async function getUpdateGuardInstructionAsync<
  TAccountGuard extends string,
  TAccountMint extends string,
  TAccountTokenAccount extends string,
  TAccountGuardAuthority extends string,
  TAccountTokenProgram extends string,
  TAccountSystemProgram extends string,
  TProgramAddress extends Address = typeof WEN_TRANSFER_GUARD_PROGRAM_ADDRESS,
>(
  input: UpdateGuardAsyncInput<
    TAccountGuard,
    TAccountMint,
    TAccountTokenAccount,
    TAccountGuardAuthority,
    TAccountTokenProgram,
    TAccountSystemProgram
  >,
  config?: { programAddress?: TProgramAddress }
): Promise<
  UpdateGuardInstruction<
    TProgramAddress,
    TAccountGuard,
    TAccountMint,
    TAccountTokenAccount,
    TAccountGuardAuthority,
    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: false },
    tokenAccount: { value: input.tokenAccount ?? null, isWritable: false },
    guardAuthority: { value: input.guardAuthority ?? 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.tokenAccount.value) {
    accounts.tokenAccount.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.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.tokenAccount),
      getAccountMeta(accounts.guardAuthority),
      getAccountMeta(accounts.tokenProgram),
      getAccountMeta(accounts.systemProgram),
    ],
    programAddress,
    data: getUpdateGuardInstructionDataEncoder().encode(
      args as UpdateGuardInstructionDataArgs
    ),
  } as UpdateGuardInstruction<
    TProgramAddress,
    TAccountGuard,
    TAccountMint,
    TAccountTokenAccount,
    TAccountGuardAuthority,
    TAccountTokenProgram,
    TAccountSystemProgram
  >;

  return instruction;
}

export type UpdateGuardInput<
  TAccountGuard extends string = string,
  TAccountMint extends string = string,
  TAccountTokenAccount extends string = string,
  TAccountGuardAuthority extends string = string,
  TAccountTokenProgram extends string = string,
  TAccountSystemProgram extends string = string,
> = {
  guard: Address<TAccountGuard>;
  mint: Address<TAccountMint>;
  tokenAccount: Address<TAccountTokenAccount>;
  guardAuthority: TransactionSigner<TAccountGuardAuthority>;
  tokenProgram?: Address<TAccountTokenProgram>;
  systemProgram?: Address<TAccountSystemProgram>;
  cpiRule: UpdateGuardInstructionDataArgs['cpiRule'];
  transferAmountRule: UpdateGuardInstructionDataArgs['transferAmountRule'];
  additionalFieldsRule: UpdateGuardInstructionDataArgs['additionalFieldsRule'];
};

export function getUpdateGuardInstruction<
  TAccountGuard extends string,
  TAccountMint extends string,
  TAccountTokenAccount extends string,
  TAccountGuardAuthority extends string,
  TAccountTokenProgram extends string,
  TAccountSystemProgram extends string,
  TProgramAddress extends Address = typeof WEN_TRANSFER_GUARD_PROGRAM_ADDRESS,
>(
  input: UpdateGuardInput<
    TAccountGuard,
    TAccountMint,
    TAccountTokenAccount,
    TAccountGuardAuthority,
    TAccountTokenProgram,
    TAccountSystemProgram
  >,
  config?: { programAddress?: TProgramAddress }
): UpdateGuardInstruction<
  TProgramAddress,
  TAccountGuard,
  TAccountMint,
  TAccountTokenAccount,
  TAccountGuardAuthority,
  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: false },
    tokenAccount: { value: input.tokenAccount ?? null, isWritable: false },
    guardAuthority: { value: input.guardAuthority ?? 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.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.tokenAccount),
      getAccountMeta(accounts.guardAuthority),
      getAccountMeta(accounts.tokenProgram),
      getAccountMeta(accounts.systemProgram),
    ],
    programAddress,
    data: getUpdateGuardInstructionDataEncoder().encode(
      args as UpdateGuardInstructionDataArgs
    ),
  } as UpdateGuardInstruction<
    TProgramAddress,
    TAccountGuard,
    TAccountMint,
    TAccountTokenAccount,
    TAccountGuardAuthority,
    TAccountTokenProgram,
    TAccountSystemProgram
  >;

  return instruction;
}

export type ParsedUpdateGuardInstruction<
  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];
    tokenAccount: TAccountMetas[2];
    guardAuthority: TAccountMetas[3];
    tokenProgram: TAccountMetas[4];
    systemProgram: TAccountMetas[5];
  };
  data: UpdateGuardInstructionData;
};

export function parseUpdateGuardInstruction<
  TProgram extends string,
  TAccountMetas extends readonly AccountMeta[],
>(
  instruction: Instruction<TProgram> &
    InstructionWithAccounts<TAccountMetas> &
    InstructionWithData<ReadonlyUint8Array>
): ParsedUpdateGuardInstruction<TProgram, TAccountMetas> {
  if (instruction.accounts.length < 6) {
    // 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(),
      tokenAccount: getNextAccount(),
      guardAuthority: getNextAccount(),
      tokenProgram: getNextAccount(),
      systemProgram: getNextAccount(),
    },
    data: getUpdateGuardInstructionDataDecoder().decode(instruction.data),
  };
}
