import { ValidationError } from '../../errors'

import {
  Account,
  BaseTransaction,
  isAccount,
  isNumber,
  validateBaseTransaction,
  validateOptionalField,
} from './common'

/**
 * Enum for AccountSet Flags.
 *
 * @category Transaction Flags
 */
export enum AccountSetAsfFlags {
  /** Require a destination tag to send transactions to this account. */
  asfRequireDest = 1,
  /**
   * Require authorization for users to hold balances issued by this address
   * can only be enabled if the address has no trust lines connected to it.
   */
  asfRequireAuth = 2,
  /** XAH should not be sent to this account. */
  asfDisallowXAH = 3,
  /**
   * Disallow use of the master key pair. Can only be enabled if the account
   * has configured another way to sign transactions, such as a Regular Key or a
   * Signer List.
   */
  asfDisableMaster = 4,
  /**
   * Track the ID of this account's most recent transaction. Required for
   * AccountTxnID.
   */
  asfAccountTxnID = 5,
  /**
   * Permanently give up the ability to freeze individual trust lines or
   * disable Global Freeze. This flag can never be disabled after being enabled.
   */
  asfNoFreeze = 6,
  /** Freeze all assets issued by this account. */
  asfGlobalFreeze = 7,
  /** Enable rippling on this account's trust lines by default. */
  asfDefaultRipple = 8,
  /** Enable Deposit Authorization on this account. */
  asfDepositAuth = 9,
  /**
   * Allow another account to mint and burn tokens on behalf of this account.
   */
  asfAuthorizedNFTokenMinter = 10,
  /** asf 11 is reserved for Hooks amendment */
  asfTshCollect = 11,
  /** Disallow other accounts from creating incoming NFTOffers */
  asfDisallowIncomingNFTokenOffer = 12,
  /** Disallow other accounts from creating incoming Checks */
  asfDisallowIncomingCheck = 13,
  /** Disallow other accounts from creating incoming PayChannels */
  asfDisallowIncomingPayChan = 14,
  /** Disallow other accounts from creating incoming Trustlines */
  asfDisallowIncomingTrustline = 15,
  /** Disallow other accounts from sending incoming Remits */
  asfDisallowIncomingRemit = 16,
  /** Permanently gain the ability to claw back issued IOUs */
  asfAllowTrustLineClawback = 17,
}

/**
 * Enum for AccountSet Transaction Flags.
 *
 * @category Transaction Flags
 */
export enum AccountSetTfFlags {
  /** The same as SetFlag: asfRequireDest. */
  tfRequireDestTag = 0x00010000,
  /** The same as ClearFlag: asfRequireDest. */
  tfOptionalDestTag = 0x00020000,
  /** The same as SetFlag: asfRequireAuth. */
  tfRequireAuth = 0x00040000,
  /** The same as ClearFlag: asfRequireAuth. */
  tfOptionalAuth = 0x00080000,
  /** The same as SetFlag: asfDisallowXAH. */
  tfDisallowXAH = 0x00100000,
  /** The same as ClearFlag: asfDisallowXAH. */
  tfAllowXAH = 0x00200000,
}

/**
 * Map of flags to boolean values representing {@link AccountSet} transaction
 * flags.
 *
 * @category Transaction Flags
 *
 * @example
 * ```typescript
 *  const accountSetTx: AccountSet = {
 *    TransactionType: 'AccountSet',
 *    Account: 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn',
 *    Flags: {
 *      tfOptionalDestTag: true,
 *      tfRequireAuth: true
 *    },
 *  }
 *
 * // Autofill the tx to see how flags actually look compared to the interface usage.
 * const autofilledTx = await client.autofill(accountSetTx)
 * console.log(autofilledTx)
 * // {
 * //  TransactionType: 'AccountSet',
 * //  Account: 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn',
 * //  Flags: 393216,
 * //  Sequence: 1,
 * //  Fee: '12',
 * //  LastLedgerSequence: 21971793
 * // }
 * ```
 */
export interface AccountSetFlagsInterface {
  tfRequireDestTag?: boolean
  tfOptionalDestTag?: boolean
  tfRequireAuth?: boolean
  tfOptionalAuth?: boolean
  tfDisallowXAH?: boolean
  tfAllowXAH?: boolean
}

/**
 * An AccountSet transaction modifies the properties of an account in the XAH
 * Ledger.
 *
 * @category Transaction Models
 */
export interface AccountSet extends BaseTransaction {
  TransactionType: 'AccountSet'
  Flags?: number | AccountSetFlagsInterface
  /** Unique identifier of a flag to disable for this account. */
  ClearFlag?: number
  /**
   * The domain that owns this account, as a string of hex representing the.
   * ASCII for the domain in lowercase.
   */
  Domain?: string
  /** Hash of an email address to be used for generating an avatar image. */
  EmailHash?: string
  /** Public key for sending encrypted messages to this account. */
  MessageKey?: string
  /** Integer flag to enable for this account. */
  SetFlag?: AccountSetAsfFlags
  /**
   * The fee to charge when users transfer this account's issued currencies,
   * represented as billionths of a unit. Cannot be more than 2000000000 or less
   * than 1000000000, except for the special case 0 meaning no fee.
   */
  TransferRate?: number
  /**
   * Tick size to use for offers involving a currency issued by this address.
   * The exchange rates of those offers is rounded to this many significant
   * digits. Valid values are 3 to 15 inclusive, or 0 to disable.
   */
  TickSize?: number
  /**
   * Sets an alternate account that is allowed to mint NFTokens on this
   * account's behalf using NFTokenMint's `Issuer` field.
   */
  NFTokenMinter?: Account
  /**
   * The allowed scale of the hook state.
   */
  HookStateScale?: number
}

const MIN_TICK_SIZE = 3
const MAX_TICK_SIZE = 15
const MAX_HOOK_STATE_SCALE = 16

/**
 * Verify the form and type of an AccountSet at runtime.
 *
 * @param tx - An AccountSet Transaction.
 * @throws When the AccountSet is Malformed.
 */
// eslint-disable-next-line max-lines-per-function, max-statements -- okay for this method, only a little over
export function validateAccountSet(tx: Record<string, unknown>): void {
  validateBaseTransaction(tx)

  validateOptionalField(tx, 'NFTokenMinter', isAccount)

  if (tx.ClearFlag !== undefined) {
    if (typeof tx.ClearFlag !== 'number') {
      throw new ValidationError('AccountSet: invalid ClearFlag')
    }
    if (!Object.values(AccountSetAsfFlags).includes(tx.ClearFlag)) {
      throw new ValidationError('AccountSet: invalid ClearFlag')
    }
  }

  if (tx.Domain !== undefined && typeof tx.Domain !== 'string') {
    throw new ValidationError('AccountSet: invalid Domain')
  }

  if (tx.EmailHash !== undefined && typeof tx.EmailHash !== 'string') {
    throw new ValidationError('AccountSet: invalid EmailHash')
  }

  if (tx.MessageKey !== undefined && typeof tx.MessageKey !== 'string') {
    throw new ValidationError('AccountSet: invalid MessageKey')
  }

  if (tx.SetFlag !== undefined) {
    if (typeof tx.SetFlag !== 'number') {
      throw new ValidationError('AccountSet: invalid SetFlag')
    }
    if (!Object.values(AccountSetAsfFlags).includes(tx.SetFlag)) {
      throw new ValidationError('AccountSet: invalid SetFlag')
    }
  }

  if (tx.TransferRate !== undefined && typeof tx.TransferRate !== 'number') {
    throw new ValidationError('AccountSet: invalid TransferRate')
  }

  if (tx.TickSize !== undefined) {
    if (typeof tx.TickSize !== 'number') {
      throw new ValidationError('AccountSet: invalid TickSize')
    }
    if (
      tx.TickSize !== 0 &&
      (tx.TickSize < MIN_TICK_SIZE || tx.TickSize > MAX_TICK_SIZE)
    ) {
      throw new ValidationError('AccountSet: invalid TickSize')
    }
  }

  validateOptionalField(tx, 'HookStateScale', isNumber)
  if (
    typeof tx.HookStateScale === 'number' &&
    tx.HookStateScale > MAX_HOOK_STATE_SCALE
  ) {
    throw new ValidationError(
      `AccountSet: HookStateScale must be less than ${MAX_HOOK_STATE_SCALE}`,
    )
  }
}
