import BN from 'bn.js';
import { AccountMeta, Address, Instruction, TransactionSigner } from '@solana/kit';
import { DepositAccounts, WithdrawAccounts, WithdrawFromAvailableAccounts } from '../@codegen/kvault/instructions';
import Decimal from 'decimal.js/decimal';

/** the populateLUTIxs should be executed in a separate transaction as we cannot create and populate a lookup table in the same tx */
export type InitVaultIxs = {
  createAtaIfNeededIxs: Instruction[];
  initVaultIxs: Instruction[];
  createLUTIx: Instruction;
  populateLUTIxs: Instruction[];
  cleanupIxs: Instruction[];
  initSharesMetadataIx: Instruction;
  createVaultFarms: CreateVaultFarms;
  setFarmToVaultIxs: SetFarmsToVaultIxs;
};

export type CreateVaultFarms = {
  createVaultFarmIxs: CreateVaultFarm;
  createFLCVaultFarmIxs?: CreateVaultFarm;
};

export type SetFarmsToVaultIxs = {
  setFarmToVaultIx: Instruction;
  setFLCFarmToVaultIx?: Instruction;
};

export type AcceptVaultOwnershipIxs = {
  acceptVaultOwnershipIx: Instruction;
  acceptFLCFarmOwnershipIx?: Instruction;
  initNewLUTIx: Instruction;
  updateLUTIxs: Instruction[]; // this has to be executed in a transaction after the initNewLUTIx is executed
};

export type UpdateReserveAllocationIxs = {
  updateReserveAllocationIx: Instruction;
  updateLUTIxs: Instruction[];
};

export type WithdrawAndBlockReserveIxs = {
  updateReserveAllocationIxs: Instruction[];
  investIxs: Instruction[];
};

export type DisinvestAllReservesIxs = {
  updateReserveAllocationIxs: Instruction[];
  investIxs: Instruction[];
};

export type UpdateVaultConfigIxs = {
  updateVaultConfigIx: Instruction;
  updateLUTIxs: Instruction[];
  extraIxs: Instruction[];
};

export type VaultComputedAllocation = {
  targetUnallocatedAmount: Decimal;
  targetReservesAllocation: Map<Address, Decimal>;
};

/** If there are ixs to setup the LUT it means it doesn't already exist and it needs to be created in a separate tx before inserting into it */
export type SyncVaultLUTIxs = {
  setupLUTIfNeededIxs: Instruction[];
  syncLUTIxs: Instruction[];
};

/** If present, execute exactly one of the stake arrays after deposit so the received shares are staked in the selected farm. */
export type DepositIxs = {
  depositIxs: Instruction[];
  stakeInFarmIfNeededIxs: Instruction[];
  stakeInFlcFarmIfNeededIxs: Instruction[]; // if the vault has a firstLossCapital farm, these ixs will stake the shares in the flc farm
};

/** The ixs to unstake shares from the selected farm and withdraw them from the vault. `unstakeFromFarmIfNeededIxs` should come before `withdrawIxs`. */
export type WithdrawIxs = {
  unstakeFromFarmIfNeededIxs: Instruction[];
  withdrawIxs: Instruction[];
  postWithdrawIxs: Instruction[]; // if needed: wSOL ATA close ix + share ATA close ix
};

/** The shares an user has in a vault (staked and unstaked), in tokens */
export type UserSharesForVault = {
  unstakedShares: Decimal;
  stakedShares: Decimal;
  totalShares: Decimal;
};

export type ReserveAllocationOverview = {
  targetWeight: Decimal;
  tokenAllocationCap: Decimal;
  ctokenAllocation: Decimal;
};

export type APYs = {
  grossAPY: Decimal;
  netAPY: Decimal;
};

export type CreateVaultFarm = {
  farm: TransactionSigner;
  setupFarmIxs: Instruction[];
  updateFarmIxs: Instruction[];
};

/// everything in lamports
// the first refresh obligation may not need the dst reserve for the first refresh, but after the first refresh it does as it borrowed from the first reserve
export type RefreshObligationIxs = {
  firstRefreshObligationIx?: Instruction;
  refreshObligationIx?: Instruction;
};

export type VaultReleaseCheckResult = {
  errors: string[];
  warnings: string[];
  success: boolean;
};

export type AllDepositAccounts = {
  depositAccounts: DepositAccounts;
  remainingAccounts: AccountMeta[];
  stakeSharesIxs?: Instruction[];
  stakeInFlcFarmIxs?: Instruction[];
};

export type AllWithdrawAccounts = {
  withdrawAccounts: WithdrawAccounts | WithdrawFromAvailableAccounts;
  remainingAccounts: AccountMeta[];
  unstakeSharesIxs?: Instruction[];
};

export type RedeemInKindReserveIx = {
  ix: Instruction;
  reserve: Address;
  ctokenAmount: BN;
};

export type RedeemInKindIxs = {
  setupIxs: Instruction[];
  redeemInKindIxs: RedeemInKindReserveIx[];
  cleanupIxs: Instruction[];
  luts: Address[];
};

export type WithdrawAndRedeemInKindIxs = {
  withdrawIxs: WithdrawIxs;
  redeemInKindIxs: RedeemInKindIxs;
  /** Shares that could not be exited after routing as much as possible through
   *  withdraw and redeem-in-kind. Zero when the exit is complete. */
  skippedShares: Decimal;
};

export type EnqueueToWithdrawIxs = {
  setupIxs: Instruction[];
  enqueueIxs: Instruction[];
  cleanupIxs: Instruction[];
};

export type WithdrawRedeemAndEnqueueIxs = {
  withdrawIxs: WithdrawIxs;
  redeemInKindIxs: RedeemInKindIxs;
  enqueueIxs: EnqueueToWithdrawIxs;
  /** Shares that could not be exited after routing as much as possible through
   *  withdraw and redeem-in-kind. Zero when the exit is complete. */
  skippedShares: Decimal;
};
