/**
 * 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 { getProgramDerivedAddress, type Address } from '@solana/kit';
import {
  combineCodec,
  fixDecoderSize,
  fixEncoderSize,
  getBytesDecoder,
  getBytesEncoder,
  getStructDecoder,
  getStructEncoder,
  transformEncoder,
  type Codec,
  type Decoder,
  type Encoder,
  type ReadonlyUint8Array,
} from '@solana/kit';
import {
  type IAccountMeta,
  type IInstruction,
  type IInstructionWithAccounts,
  type IInstructionWithData,
  type ReadonlyAccount,
  type WritableAccount,
  type WritableSignerAccount,
} from '@solana/kit';
import { type IAccountSignerMeta, type TransactionSigner } from '@solana/kit';
import { SINGLE_CHAIN_GUARD_PROGRAM_ADDRESS } from '../programs/index.js';
import { getAccountMetaFactory, type ResolvedAccount } from '../shared/index.js';

export const INITIALIZE_DISCRIMINATOR = new Uint8Array([175, 175, 109, 31, 13, 152, 155, 237]);

export function getInitializeDiscriminatorBytes() {
  return fixEncoderSize(getBytesEncoder(), 8).encode(INITIALIZE_DISCRIMINATOR);
}

export type InitializeInstruction<
  TProgram extends string = typeof SINGLE_CHAIN_GUARD_PROGRAM_ADDRESS,
  TAccountInitialAdmin extends string | IAccountMeta<string> = string,
  TAccountAdminSingleton extends string | IAccountMeta<string> = string,
  TAccountSystemProgram extends string | IAccountMeta<string> = '11111111111111111111111111111111',
  TAccountProgram extends string | IAccountMeta<string> = 'EaSKMioKTLtbFXVaMEmHjSdZDEoZHpSmQBu6NfsND87n',
  TAccountProgramData extends string | IAccountMeta<string> = string,
  TRemainingAccounts extends readonly IAccountMeta<string>[] = [],
> = IInstruction<TProgram> &
  IInstructionWithData<Uint8Array> &
  IInstructionWithAccounts<
    [
      TAccountInitialAdmin extends string
        ? WritableSignerAccount<TAccountInitialAdmin> & IAccountSignerMeta<TAccountInitialAdmin>
        : TAccountInitialAdmin,
      TAccountAdminSingleton extends string ? WritableAccount<TAccountAdminSingleton> : TAccountAdminSingleton,
      TAccountSystemProgram extends string ? ReadonlyAccount<TAccountSystemProgram> : TAccountSystemProgram,
      TAccountProgram extends string ? ReadonlyAccount<TAccountProgram> : TAccountProgram,
      TAccountProgramData extends string ? ReadonlyAccount<TAccountProgramData> : TAccountProgramData,
      ...TRemainingAccounts,
    ]
  >;

export type InitializeInstructionData = { discriminator: ReadonlyUint8Array };

export type InitializeInstructionDataArgs = {};

export function getInitializeInstructionDataEncoder(): Encoder<InitializeInstructionDataArgs> {
  return transformEncoder(getStructEncoder([['discriminator', fixEncoderSize(getBytesEncoder(), 8)]]), (value) => ({
    ...value,
    discriminator: INITIALIZE_DISCRIMINATOR,
  }));
}

export function getInitializeInstructionDataDecoder(): Decoder<InitializeInstructionData> {
  return getStructDecoder([['discriminator', fixDecoderSize(getBytesDecoder(), 8)]]);
}

export function getInitializeInstructionDataCodec(): Codec<InitializeInstructionDataArgs, InitializeInstructionData> {
  return combineCodec(getInitializeInstructionDataEncoder(), getInitializeInstructionDataDecoder());
}

export type InitializeAsyncInput<
  TAccountInitialAdmin extends string = string,
  TAccountAdminSingleton extends string = string,
  TAccountSystemProgram extends string = string,
  TAccountProgram extends string = string,
  TAccountProgramData extends string = string,
> = {
  /** Admin, creating Guard */
  initialAdmin: TransactionSigner<TAccountInitialAdmin>;
  /** Account that will store guard data */
  adminSingleton?: Address<TAccountAdminSingleton>;
  /** Solana System program */
  systemProgram?: Address<TAccountSystemProgram>;
  /** This program */
  program?: Address<TAccountProgram>;
  /**
   * This program ProgramData account to check that only current upgrade authority
   * can initialize Admin
   */
  programData: Address<TAccountProgramData>;
};

export async function getInitializeInstructionAsync<
  TAccountInitialAdmin extends string,
  TAccountAdminSingleton extends string,
  TAccountSystemProgram extends string,
  TAccountProgram extends string,
  TAccountProgramData extends string,
  TProgramAddress extends Address = typeof SINGLE_CHAIN_GUARD_PROGRAM_ADDRESS,
>(
  input: InitializeAsyncInput<
    TAccountInitialAdmin,
    TAccountAdminSingleton,
    TAccountSystemProgram,
    TAccountProgram,
    TAccountProgramData
  >,
  config?: { programAddress?: TProgramAddress },
): Promise<
  InitializeInstruction<
    TProgramAddress,
    TAccountInitialAdmin,
    TAccountAdminSingleton,
    TAccountSystemProgram,
    TAccountProgram,
    TAccountProgramData
  >
> {
  // Program address.
  const programAddress = config?.programAddress ?? SINGLE_CHAIN_GUARD_PROGRAM_ADDRESS;

  // Original accounts.
  const originalAccounts = {
    initialAdmin: { value: input.initialAdmin ?? null, isWritable: true },
    adminSingleton: { value: input.adminSingleton ?? null, isWritable: true },
    systemProgram: { value: input.systemProgram ?? null, isWritable: false },
    program: { value: input.program ?? null, isWritable: false },
    programData: { value: input.programData ?? null, isWritable: false },
  };
  const accounts = originalAccounts as Record<keyof typeof originalAccounts, ResolvedAccount>;

  // Resolve default values.
  if (!accounts.adminSingleton.value) {
    accounts.adminSingleton.value = await getProgramDerivedAddress({
      programAddress,
      seeds: [getBytesEncoder().encode(new Uint8Array([97, 100, 109, 105, 110]))],
    });
  }
  if (!accounts.systemProgram.value) {
    accounts.systemProgram.value = '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>;
  }
  if (!accounts.program.value) {
    accounts.program.value =
      'EaSKMioKTLtbFXVaMEmHjSdZDEoZHpSmQBu6NfsND87n' as Address<'EaSKMioKTLtbFXVaMEmHjSdZDEoZHpSmQBu6NfsND87n'>;
  }

  const getAccountMeta = getAccountMetaFactory(programAddress, 'programId');
  const instruction = {
    accounts: [
      getAccountMeta(accounts.initialAdmin),
      getAccountMeta(accounts.adminSingleton),
      getAccountMeta(accounts.systemProgram),
      getAccountMeta(accounts.program),
      getAccountMeta(accounts.programData),
    ],
    programAddress,
    data: getInitializeInstructionDataEncoder().encode({}),
  } as InitializeInstruction<
    TProgramAddress,
    TAccountInitialAdmin,
    TAccountAdminSingleton,
    TAccountSystemProgram,
    TAccountProgram,
    TAccountProgramData
  >;

  return instruction;
}

export type InitializeInput<
  TAccountInitialAdmin extends string = string,
  TAccountAdminSingleton extends string = string,
  TAccountSystemProgram extends string = string,
  TAccountProgram extends string = string,
  TAccountProgramData extends string = string,
> = {
  /** Admin, creating Guard */
  initialAdmin: TransactionSigner<TAccountInitialAdmin>;
  /** Account that will store guard data */
  adminSingleton: Address<TAccountAdminSingleton>;
  /** Solana System program */
  systemProgram?: Address<TAccountSystemProgram>;
  /** This program */
  program?: Address<TAccountProgram>;
  /**
   * This program ProgramData account to check that only current upgrade authority
   * can initialize Admin
   */
  programData: Address<TAccountProgramData>;
};

export function getInitializeInstruction<
  TAccountInitialAdmin extends string,
  TAccountAdminSingleton extends string,
  TAccountSystemProgram extends string,
  TAccountProgram extends string,
  TAccountProgramData extends string,
  TProgramAddress extends Address = typeof SINGLE_CHAIN_GUARD_PROGRAM_ADDRESS,
>(
  input: InitializeInput<
    TAccountInitialAdmin,
    TAccountAdminSingleton,
    TAccountSystemProgram,
    TAccountProgram,
    TAccountProgramData
  >,
  config?: { programAddress?: TProgramAddress },
): InitializeInstruction<
  TProgramAddress,
  TAccountInitialAdmin,
  TAccountAdminSingleton,
  TAccountSystemProgram,
  TAccountProgram,
  TAccountProgramData
> {
  // Program address.
  const programAddress = config?.programAddress ?? SINGLE_CHAIN_GUARD_PROGRAM_ADDRESS;

  // Original accounts.
  const originalAccounts = {
    initialAdmin: { value: input.initialAdmin ?? null, isWritable: true },
    adminSingleton: { value: input.adminSingleton ?? null, isWritable: true },
    systemProgram: { value: input.systemProgram ?? null, isWritable: false },
    program: { value: input.program ?? null, isWritable: false },
    programData: { value: input.programData ?? null, isWritable: false },
  };
  const accounts = originalAccounts as Record<keyof typeof originalAccounts, ResolvedAccount>;

  // Resolve default values.
  if (!accounts.systemProgram.value) {
    accounts.systemProgram.value = '11111111111111111111111111111111' as Address<'11111111111111111111111111111111'>;
  }
  if (!accounts.program.value) {
    accounts.program.value =
      'EaSKMioKTLtbFXVaMEmHjSdZDEoZHpSmQBu6NfsND87n' as Address<'EaSKMioKTLtbFXVaMEmHjSdZDEoZHpSmQBu6NfsND87n'>;
  }

  const getAccountMeta = getAccountMetaFactory(programAddress, 'programId');
  const instruction = {
    accounts: [
      getAccountMeta(accounts.initialAdmin),
      getAccountMeta(accounts.adminSingleton),
      getAccountMeta(accounts.systemProgram),
      getAccountMeta(accounts.program),
      getAccountMeta(accounts.programData),
    ],
    programAddress,
    data: getInitializeInstructionDataEncoder().encode({}),
  } as InitializeInstruction<
    TProgramAddress,
    TAccountInitialAdmin,
    TAccountAdminSingleton,
    TAccountSystemProgram,
    TAccountProgram,
    TAccountProgramData
  >;

  return instruction;
}

export type ParsedInitializeInstruction<
  TProgram extends string = typeof SINGLE_CHAIN_GUARD_PROGRAM_ADDRESS,
  TAccountMetas extends readonly IAccountMeta[] = readonly IAccountMeta[],
> = {
  programAddress: Address<TProgram>;
  accounts: {
    /** Admin, creating Guard */
    initialAdmin: TAccountMetas[0];
    /** Account that will store guard data */
    adminSingleton: TAccountMetas[1];
    /** Solana System program */
    systemProgram: TAccountMetas[2];
    /** This program */
    program: TAccountMetas[3];
    /**
     * This program ProgramData account to check that only current upgrade authority
     * can initialize Admin
     */

    programData: TAccountMetas[4];
  };
  data: InitializeInstructionData;
};

export function parseInitializeInstruction<TProgram extends string, TAccountMetas extends readonly IAccountMeta[]>(
  instruction: IInstruction<TProgram> & IInstructionWithAccounts<TAccountMetas> & IInstructionWithData<Uint8Array>,
): ParsedInitializeInstruction<TProgram, TAccountMetas> {
  if (instruction.accounts.length < 5) {
    // 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: {
      initialAdmin: getNextAccount(),
      adminSingleton: getNextAccount(),
      systemProgram: getNextAccount(),
      program: getNextAccount(),
      programData: getNextAccount(),
    },
    data: getInitializeInstructionDataDecoder().decode(instruction.data),
  };
}
