/**
 * 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,
  getAddressDecoder,
  getAddressEncoder,
  getBytesDecoder,
  getBytesEncoder,
  getProgramDerivedAddress,
  getStructDecoder,
  getStructEncoder,
  getU32Decoder,
  getU32Encoder,
  getU8Decoder,
  getU8Encoder,
  transformEncoder,
  type AccountMeta,
  type AccountSignerMeta,
  type Address,
  type Codec,
  type Decoder,
  type Encoder,
  type Instruction,
  type InstructionWithAccounts,
  type InstructionWithData,
  type ReadonlyAccount,
  type ReadonlySignerAccount,
  type ReadonlyUint8Array,
  type TransactionSigner,
} from "@solana/kit";
import { findSelfAuthorityPda } from "../pdas";
import { SVM_SPOKE_PROGRAM_ADDRESS } from "../programs";
import { getAccountMetaFactory, type ResolvedAccount } from "../shared";

export const HANDLE_RECEIVE_MESSAGE_DISCRIMINATOR = new Uint8Array([
  133, 102, 1, 180, 145, 11, 138, 180,
]);

export function getHandleReceiveMessageDiscriminatorBytes() {
  return fixEncoderSize(getBytesEncoder(), 8).encode(
    HANDLE_RECEIVE_MESSAGE_DISCRIMINATOR,
  );
}

export type HandleReceiveMessageInstruction<
  TProgram extends string = typeof SVM_SPOKE_PROGRAM_ADDRESS,
  TAccountAuthorityPda extends string | AccountMeta<string> = string,
  TAccountState extends string | AccountMeta<string> = string,
  TAccountSelfAuthority extends string | AccountMeta<string> = string,
  TAccountProgram extends string | AccountMeta<string> =
    "DLv3NggMiSaef97YCkew5xKUHDh13tVGZ7tydt3ZeAru",
  TRemainingAccounts extends readonly AccountMeta<string>[] = [],
> = Instruction<TProgram> &
  InstructionWithData<ReadonlyUint8Array> &
  InstructionWithAccounts<
    [
      TAccountAuthorityPda extends string
        ? ReadonlySignerAccount<TAccountAuthorityPda> &
            AccountSignerMeta<TAccountAuthorityPda>
        : TAccountAuthorityPda,
      TAccountState extends string
        ? ReadonlyAccount<TAccountState>
        : TAccountState,
      TAccountSelfAuthority extends string
        ? ReadonlyAccount<TAccountSelfAuthority>
        : TAccountSelfAuthority,
      TAccountProgram extends string
        ? ReadonlyAccount<TAccountProgram>
        : TAccountProgram,
      ...TRemainingAccounts,
    ]
  >;

export type HandleReceiveMessageInstructionData = {
  discriminator: ReadonlyUint8Array;
  remoteDomain: number;
  sender: Address;
  messageBody: ReadonlyUint8Array;
  authorityBump: number;
};

export type HandleReceiveMessageInstructionDataArgs = {
  remoteDomain: number;
  sender: Address;
  messageBody: ReadonlyUint8Array;
  authorityBump: number;
};

export function getHandleReceiveMessageInstructionDataEncoder(): Encoder<HandleReceiveMessageInstructionDataArgs> {
  return transformEncoder(
    getStructEncoder([
      ["discriminator", fixEncoderSize(getBytesEncoder(), 8)],
      ["remoteDomain", getU32Encoder()],
      ["sender", getAddressEncoder()],
      ["messageBody", addEncoderSizePrefix(getBytesEncoder(), getU32Encoder())],
      ["authorityBump", getU8Encoder()],
    ]),
    (value) => ({
      ...value,
      discriminator: HANDLE_RECEIVE_MESSAGE_DISCRIMINATOR,
    }),
  );
}

export function getHandleReceiveMessageInstructionDataDecoder(): Decoder<HandleReceiveMessageInstructionData> {
  return getStructDecoder([
    ["discriminator", fixDecoderSize(getBytesDecoder(), 8)],
    ["remoteDomain", getU32Decoder()],
    ["sender", getAddressDecoder()],
    ["messageBody", addDecoderSizePrefix(getBytesDecoder(), getU32Decoder())],
    ["authorityBump", getU8Decoder()],
  ]);
}

export function getHandleReceiveMessageInstructionDataCodec(): Codec<
  HandleReceiveMessageInstructionDataArgs,
  HandleReceiveMessageInstructionData
> {
  return combineCodec(
    getHandleReceiveMessageInstructionDataEncoder(),
    getHandleReceiveMessageInstructionDataDecoder(),
  );
}

export type HandleReceiveMessageAsyncInput<
  TAccountAuthorityPda extends string = string,
  TAccountState extends string = string,
  TAccountSelfAuthority extends string = string,
  TAccountProgram extends string = string,
> = {
  authorityPda?: TransactionSigner<TAccountAuthorityPda>;
  state: Address<TAccountState>;
  selfAuthority?: Address<TAccountSelfAuthority>;
  program?: Address<TAccountProgram>;
  remoteDomain: HandleReceiveMessageInstructionDataArgs["remoteDomain"];
  sender: HandleReceiveMessageInstructionDataArgs["sender"];
  messageBody: HandleReceiveMessageInstructionDataArgs["messageBody"];
  authorityBump: HandleReceiveMessageInstructionDataArgs["authorityBump"];
};

export async function getHandleReceiveMessageInstructionAsync<
  TAccountAuthorityPda extends string,
  TAccountState extends string,
  TAccountSelfAuthority extends string,
  TAccountProgram extends string,
  TProgramAddress extends Address = typeof SVM_SPOKE_PROGRAM_ADDRESS,
>(
  input: HandleReceiveMessageAsyncInput<
    TAccountAuthorityPda,
    TAccountState,
    TAccountSelfAuthority,
    TAccountProgram
  >,
  config?: { programAddress?: TProgramAddress },
): Promise<
  HandleReceiveMessageInstruction<
    TProgramAddress,
    TAccountAuthorityPda,
    TAccountState,
    TAccountSelfAuthority,
    TAccountProgram
  >
> {
  // Program address.
  const programAddress = config?.programAddress ?? SVM_SPOKE_PROGRAM_ADDRESS;

  // Original accounts.
  const originalAccounts = {
    authorityPda: { value: input.authorityPda ?? null, isWritable: false },
    state: { value: input.state ?? null, isWritable: false },
    selfAuthority: { value: input.selfAuthority ?? null, isWritable: false },
    program: { value: input.program ?? null, isWritable: false },
  };
  const accounts = originalAccounts as Record<
    keyof typeof originalAccounts,
    ResolvedAccount
  >;

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

  // Resolve default values.
  if (!accounts.authorityPda.value) {
    accounts.authorityPda.value = await getProgramDerivedAddress({
      programAddress:
        "CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd" as Address<"CCTPmbSD7gX1bxKPAmg77w8oFzNFpaQiQUWD43TKaecd">,
      seeds: [
        getBytesEncoder().encode(
          new Uint8Array([
            109, 101, 115, 115, 97, 103, 101, 95, 116, 114, 97, 110, 115, 109,
            105, 116, 116, 101, 114, 95, 97, 117, 116, 104, 111, 114, 105, 116,
            121,
          ]),
        ),
        getBytesEncoder().encode(
          new Uint8Array([
            183, 102, 64, 134, 222, 55, 238, 112, 130, 28, 16, 68, 91, 22, 47,
            44, 126, 200, 121, 91, 208, 128, 12, 20, 98, 148, 158, 35, 40, 209,
            221, 90,
          ]),
        ),
      ],
    });
  }
  if (!accounts.selfAuthority.value) {
    accounts.selfAuthority.value = await findSelfAuthorityPda();
  }
  if (!accounts.program.value) {
    accounts.program.value =
      "DLv3NggMiSaef97YCkew5xKUHDh13tVGZ7tydt3ZeAru" as Address<"DLv3NggMiSaef97YCkew5xKUHDh13tVGZ7tydt3ZeAru">;
  }

  const getAccountMeta = getAccountMetaFactory(programAddress, "programId");
  return Object.freeze({
    accounts: [
      getAccountMeta(accounts.authorityPda),
      getAccountMeta(accounts.state),
      getAccountMeta(accounts.selfAuthority),
      getAccountMeta(accounts.program),
    ],
    data: getHandleReceiveMessageInstructionDataEncoder().encode(
      args as HandleReceiveMessageInstructionDataArgs,
    ),
    programAddress,
  } as HandleReceiveMessageInstruction<
    TProgramAddress,
    TAccountAuthorityPda,
    TAccountState,
    TAccountSelfAuthority,
    TAccountProgram
  >);
}

export type HandleReceiveMessageInput<
  TAccountAuthorityPda extends string = string,
  TAccountState extends string = string,
  TAccountSelfAuthority extends string = string,
  TAccountProgram extends string = string,
> = {
  authorityPda: TransactionSigner<TAccountAuthorityPda>;
  state: Address<TAccountState>;
  selfAuthority: Address<TAccountSelfAuthority>;
  program?: Address<TAccountProgram>;
  remoteDomain: HandleReceiveMessageInstructionDataArgs["remoteDomain"];
  sender: HandleReceiveMessageInstructionDataArgs["sender"];
  messageBody: HandleReceiveMessageInstructionDataArgs["messageBody"];
  authorityBump: HandleReceiveMessageInstructionDataArgs["authorityBump"];
};

export function getHandleReceiveMessageInstruction<
  TAccountAuthorityPda extends string,
  TAccountState extends string,
  TAccountSelfAuthority extends string,
  TAccountProgram extends string,
  TProgramAddress extends Address = typeof SVM_SPOKE_PROGRAM_ADDRESS,
>(
  input: HandleReceiveMessageInput<
    TAccountAuthorityPda,
    TAccountState,
    TAccountSelfAuthority,
    TAccountProgram
  >,
  config?: { programAddress?: TProgramAddress },
): HandleReceiveMessageInstruction<
  TProgramAddress,
  TAccountAuthorityPda,
  TAccountState,
  TAccountSelfAuthority,
  TAccountProgram
> {
  // Program address.
  const programAddress = config?.programAddress ?? SVM_SPOKE_PROGRAM_ADDRESS;

  // Original accounts.
  const originalAccounts = {
    authorityPda: { value: input.authorityPda ?? null, isWritable: false },
    state: { value: input.state ?? null, isWritable: false },
    selfAuthority: { value: input.selfAuthority ?? null, isWritable: false },
    program: { value: input.program ?? null, isWritable: false },
  };
  const accounts = originalAccounts as Record<
    keyof typeof originalAccounts,
    ResolvedAccount
  >;

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

  // Resolve default values.
  if (!accounts.program.value) {
    accounts.program.value =
      "DLv3NggMiSaef97YCkew5xKUHDh13tVGZ7tydt3ZeAru" as Address<"DLv3NggMiSaef97YCkew5xKUHDh13tVGZ7tydt3ZeAru">;
  }

  const getAccountMeta = getAccountMetaFactory(programAddress, "programId");
  return Object.freeze({
    accounts: [
      getAccountMeta(accounts.authorityPda),
      getAccountMeta(accounts.state),
      getAccountMeta(accounts.selfAuthority),
      getAccountMeta(accounts.program),
    ],
    data: getHandleReceiveMessageInstructionDataEncoder().encode(
      args as HandleReceiveMessageInstructionDataArgs,
    ),
    programAddress,
  } as HandleReceiveMessageInstruction<
    TProgramAddress,
    TAccountAuthorityPda,
    TAccountState,
    TAccountSelfAuthority,
    TAccountProgram
  >);
}

export type ParsedHandleReceiveMessageInstruction<
  TProgram extends string = typeof SVM_SPOKE_PROGRAM_ADDRESS,
  TAccountMetas extends readonly AccountMeta[] = readonly AccountMeta[],
> = {
  programAddress: Address<TProgram>;
  accounts: {
    authorityPda: TAccountMetas[0];
    state: TAccountMetas[1];
    selfAuthority: TAccountMetas[2];
    program: TAccountMetas[3];
  };
  data: HandleReceiveMessageInstructionData;
};

export function parseHandleReceiveMessageInstruction<
  TProgram extends string,
  TAccountMetas extends readonly AccountMeta[],
>(
  instruction: Instruction<TProgram> &
    InstructionWithAccounts<TAccountMetas> &
    InstructionWithData<ReadonlyUint8Array>,
): ParsedHandleReceiveMessageInstruction<TProgram, TAccountMetas> {
  if (instruction.accounts.length < 4) {
    // TODO: Coded error.
    throw new Error("Not enough accounts");
  }
  let accountIndex = 0;
  const getNextAccount = () => {
    const accountMeta = (instruction.accounts as TAccountMetas)[accountIndex]!;
    accountIndex += 1;
    return accountMeta;
  };
  return {
    programAddress: instruction.programAddress,
    accounts: {
      authorityPda: getNextAccount(),
      state: getNextAccount(),
      selfAuthority: getNextAccount(),
      program: getNextAccount(),
    },
    data: getHandleReceiveMessageInstructionDataDecoder().decode(
      instruction.data,
    ),
  };
}
