/* eslint-disable no-param-reassign -- param reassign is safe */
/* eslint-disable no-bitwise -- flags require bitwise operations */
import { ValidationError } from '../../errors'
import { Hook } from '../common/xahau'
import {
  AccountRootFlagsInterface,
  AccountRootFlags,
} from '../ledger/AccountRoot'
import { AccountSetTfFlags } from '../transactions/accountSet'
import { GlobalFlags } from '../transactions/common'
import { CronSetFlags } from '../transactions/cronSet'
import { OfferCreateFlags } from '../transactions/offerCreate'
import { PaymentFlags } from '../transactions/payment'
import { PaymentChannelClaimFlags } from '../transactions/paymentChannelClaim'
import { SetHookFlagsInterface, SetHookFlags } from '../transactions/setHook'
import {
  RemarkFlagsInterface,
  RemarkFlags,
  Remark,
} from '../transactions/setRemarks'
import type { Transaction } from '../transactions/transaction'
import { TrustSetFlags } from '../transactions/trustSet'

import { isFlagEnabled } from '.'

/**
 * Convert an AccountRoot Flags number into an interface for easy interpretation.
 *
 * @param flags - A number which is the bitwise and of all enabled AccountRootFlagsInterface.
 * @returns An interface with all flags as booleans.
 */
export function parseAccountRootFlags(
  flags: number,
): AccountRootFlagsInterface {
  const flagsInterface: AccountRootFlagsInterface = {}

  // If we use keys all will be strings and enums are reversed during transpilation
  Object.values(AccountRootFlags).forEach((flag) => {
    if (
      typeof flag === 'string' &&
      isFlagEnabled(flags, AccountRootFlags[flag])
    ) {
      flagsInterface[flag] = true
    }
  })

  return flagsInterface
}

const txToFlag = {
  AccountSet: AccountSetTfFlags,
  OfferCreate: OfferCreateFlags,
  PaymentChannelClaim: PaymentChannelClaimFlags,
  Payment: PaymentFlags,
  TrustSet: TrustSetFlags,
  CronSet: CronSetFlags,
}

/**
 * Sets a transaction's flags to its numeric representation.
 *
 * @param tx - A transaction to set its flags to its numeric representation.
 */
export function setTransactionFlagsToNumber(tx: Transaction): void {
  if (tx.Flags == null) {
    tx.Flags = 0
    return
  }
  if (typeof tx.Flags === 'number') {
    return
  }

  if (tx.TransactionType === 'SetHook') {
    tx.Flags = convertFlagsToNumber(tx.Flags, SetHookFlags)
    tx.Hooks.forEach((hook: Hook) => {
      hook.Hook.Flags = convertFlagsToNumber(
        // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- idk
        hook.Hook.Flags as SetHookFlagsInterface,
        SetHookFlags,
      )
    })
  } else if (tx.TransactionType === 'SetRemarks') {
    tx.Remarks.forEach((remark: Remark) => {
      remark.Remark.Flags = convertFlagsToNumber(
        // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- idk
        remark.Remark.Flags as RemarkFlagsInterface,
        RemarkFlags,
      )
    })
  }

  tx.Flags = txToFlag[tx.TransactionType]
    ? convertFlagsToNumber(tx.Flags, txToFlag[tx.TransactionType])
    : 0
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any -- added ValidationError check for flagEnum
function convertFlagsToNumber(flags: GlobalFlags, flagEnum: any): number {
  return Object.keys(flags).reduce((resultFlags, flag) => {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
    if (flagEnum[flag] == null) {
      throw new ValidationError(
        `flag ${flag} doesn't exist in flagEnum: ${JSON.stringify(flagEnum)}`,
      )
    }
    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
    return flags[flag] ? resultFlags | flagEnum[flag] : resultFlags
  }, 0)
}

/**
 * Convert a Transaction flags property into a map for easy interpretation.
 *
 * @param tx - A transaction to parse flags for.
 * @returns A map with all flags as booleans.
 */
export function parseTransactionFlags(tx: Transaction): object {
  setTransactionFlagsToNumber(tx)
  if (typeof tx.Flags !== 'number' || !tx.Flags || tx.Flags === 0) {
    return {}
  }

  const flags = tx.Flags
  const flagsMap = {}

  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- safe member access
  const flagEnum = txToFlag[tx.TransactionType]
  Object.values(flagEnum).forEach((flag) => {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
    if (typeof flag === 'string' && isFlagEnabled(flags, flagEnum[flag])) {
      flagsMap[flag] = true
    }
  })

  return flagsMap
}
