import {
  bigIntToNumber,
  formatBigInt,
  numberToBigInt,
  type BigIntFormatOptions,
} from '@graphprotocol/gds-utils'

import type { CurrencyInputProps } from '../components/CurrencyInput.tsx'
import {
  useNumberInput,
  type UseNumberInputError,
  type UseNumberInputOptions,
  type UseNumberInputReturnedObject,
} from './useNumberInput.ts'

export type UseCurrencyInputCurrency = {
  code: string
  format: BigIntFormatOptions
}

function normalizeCurrency(currency: string | UseCurrencyInputCurrency): UseCurrencyInputCurrency {
  if (typeof currency === 'string') {
    return { code: currency, format: {} }
  }
  return currency
}

export type UseCurrencyInputOptions<
  UseBigInt extends boolean = false,
  T = UseBigInt extends true ? bigint : number,
> = Omit<UseNumberInputOptions<UseBigInt, T>, 'format'> & {
  /** @default 0 */
  min?: T | null | undefined
  /** @default 'GRT' */
  currency?: string | UseCurrencyInputCurrency | undefined
  /** @default { code: 'USD', format: { minimumFractionDigits: 2, maximumFractionDigits: 2 } } */
  convertedCurrency?: string | UseCurrencyInputCurrency | undefined
  conversionRate?: T | ((value: T) => T) | undefined
}

export type UseCurrencyInputReturnedObject<T = number | bigint> = {
  /** `null` means the input is either empty or invalid; check `error` to know which. */
  value: T | null
  setValue: (value: T | null) => void
  convertedValue: T | null
  error: UseNumberInputError | null
  nonValidatedValue: bigint | null
  nearestValidValue: T
  props: UseNumberInputReturnedObject<T>['props'] & {
    currency: CurrencyInputProps['currency']
    convertedCurrency: CurrencyInputProps['convertedCurrency']
    convertedAmount?: CurrencyInputProps['convertedAmount'] | undefined
    onMaxClicked?: CurrencyInputProps['onMaxClicked'] | undefined
  }
}

export function useCurrencyInput<
  UseBigInt extends boolean = false,
  T = UseBigInt extends true ? bigint : number,
>({
  useBigInt = false as UseBigInt,
  precision = 18n,
  min = null,
  max = null,
  currency: passedCurrency = 'GRT',
  convertedCurrency: passedConvertedCurrency = {
    code: 'USD',
    format: { minimumFractionDigits: 2, maximumFractionDigits: 2 },
  },
  conversionRate,
  ...options
}: UseCurrencyInputOptions<UseBigInt> = {}): UseCurrencyInputReturnedObject<T> {
  type TT = UseBigInt extends true ? bigint : number

  const currency = normalizeCurrency(passedCurrency)
  const convertedCurrency = normalizeCurrency(passedConvertedCurrency)

  const numberInput = useNumberInput<UseBigInt, T>({
    useBigInt,
    precision,
    min: min ?? ((useBigInt ? 0n : 0) as TT),
    max,
    format: currency.format,
    ...options,
  })

  let convertedAmount: string | undefined = undefined
  let convertedValue: T | null = null

  if (conversionRate !== undefined) {
    let convert: (value: TT) => TT

    if (typeof conversionRate === 'number' || typeof conversionRate === 'bigint') {
      convert = (value) => {
        if (typeof value === 'bigint') {
          const precisionFactor = 10n ** precision
          return ((value * (conversionRate as bigint)) / precisionFactor) as TT
        } else {
          return (value * (conversionRate as number)) as TT
        }
      }
    } else {
      convert = conversionRate
    }

    const valueToConvert = numberInput.nonValidatedValue ?? 0n
    const nonValidatedConvertedValue = convert(
      (useBigInt ? valueToConvert : bigIntToNumber(valueToConvert, { precision })) as TT,
    )

    convertedAmount = formatBigInt(
      typeof nonValidatedConvertedValue === 'number'
        ? numberToBigInt(nonValidatedConvertedValue, { precision })
        : nonValidatedConvertedValue,
      { precision, ...convertedCurrency.format },
    )
    if (numberInput.value !== null) {
      convertedValue = nonValidatedConvertedValue as T
    }
  }

  const onMaxClicked = max !== null ? () => numberInput.setValue(max as T) : undefined

  return {
    ...numberInput,
    convertedValue,
    props: {
      ...numberInput.props,
      currency: currency.code,
      convertedCurrency: convertedCurrency.code,
      convertedAmount,
      onMaxClicked,
    },
  }
}
