// SPDX-FileCopyrightText: © 2024 LEDGER SAS // SPDX-License-Identifier: Apache-2.0 import type { Unit } from '../api/types' // Forked from @ledgerhq/live-currency-format/src/formatCurrencyUnit.ts import { BigNumber } from 'bignumber.js' import { toLocaleString } from './BigNumberToLocaleString' import { getSeparators } from './localeUtility' const nonBreakableSpace = '\u00A0' const defaultFormatOptions = { locale: 'en-EN', showCode: false, alwaysShowSign: false, showAllDigits: false, disableRounding: false, useGrouping: true, subMagnitude: 0, discreet: false, joinFragmentsSeparator: '', dynamicSignificantDigits: 6, staticSignificantDigits: 8, } export type formatCurrencyUnitOptions = Partial export type FormatterValue = { integerPart: string decimalPart: string decimalSeparator: '.' | ',' currencyText: string currencyPosition: 'start' | 'end' } type FormatState = { value: string sign: string | null code: string | null decimalSeparator: '.' | ',' currencyPosition: 'start' | 'end' } type ResolvedFormatOptions = typeof defaultFormatOptions & Unit const buildFormatState = ( unit: Unit, value: BigNumber, _options?: formatCurrencyUnitOptions ): FormatState => { const emptyState = (): FormatState => ({ value: '', sign: null, code: null, decimalSeparator: '.', currencyPosition: unit.prefixCode ? 'start' : 'end', }) if (!BigNumber.isBigNumber(value)) { console.warn('formatCurrencyUnit called with value=', value) return emptyState() } if (value.isNaN()) { console.warn('formatCurrencyUnit called with NaN value!') return emptyState() } if (!value.isFinite()) { console.warn('formatCurrencyUnit called with infinite value=', value) return emptyState() } const options: Record = {} if (_options) { let k: keyof formatCurrencyUnitOptions for (k in _options) { const optionValue = _options[k] if (optionValue !== undefined) { options[k] = optionValue } } } const resolvedOptions: ResolvedFormatOptions = { ...defaultFormatOptions, ...unit, ...options, } const { showCode, alwaysShowSign, showAllDigits, locale, disableRounding, useGrouping, subMagnitude, discreet, dynamicSignificantDigits, staticSignificantDigits, } = resolvedOptions const { magnitude, code } = unit const floatValue = value.div(new BigNumber(10).pow(magnitude)) const floatValueAbs = floatValue.abs() const minimumFractionDigits = showAllDigits ? magnitude : 0 const maximumFractionDigits = disableRounding ? magnitude + subMagnitude : Math.max( minimumFractionDigits, Math.max( 0, Math.min( dynamicSignificantDigits - Math.ceil(Math.log(floatValueAbs.toNumber()) / Math.log(10)), magnitude + subMagnitude, staticSignificantDigits ) ) ) const sign = alwaysShowSign || floatValue.isNegative() ? (floatValue.isNegative() ? '-' : '+') : null const formattedValue = discreet ? '***' : toLocaleString(floatValueAbs, locale, { maximumFractionDigits, minimumFractionDigits, useGrouping, }) const separators = getSeparators(locale) const decimalSeparator = separators.decimal === ',' ? ',' : '.' const currencyPosition = unit.prefixCode ? 'start' : 'end' return { value: formattedValue, sign, code: showCode ? code : null, decimalSeparator, currencyPosition, } } export const formatCurrencyUnitFragment = ( unit: Unit, value: BigNumber, options?: formatCurrencyUnitOptions ): FormatterValue => { const { value: formattedValue, sign, code, decimalSeparator, currencyPosition, } = buildFormatState(unit, value, options) const hasDecimals = formattedValue.indexOf(decimalSeparator) !== -1 const [integerPartRaw, decimalPartRaw] = hasDecimals ? formattedValue.split(decimalSeparator) : [formattedValue, ''] let integerPart = integerPartRaw const decimalPart = decimalPartRaw || '' let currencyText = code || '' if (sign) { if (currencyPosition === 'start' && currencyText) { currencyText = `${sign}${currencyText}` } else { integerPart = `${sign}${integerPart}` } } return { integerPart, decimalPart, decimalSeparator, currencyText, currencyPosition, } } export const formatCurrencyUnit = ( unit: Unit, value: BigNumber, options?: formatCurrencyUnitOptions ): string => { const joinFragmentsSeparator = (options && options.joinFragmentsSeparator) || defaultFormatOptions.joinFragmentsSeparator const { value: formattedValue, sign, code } = buildFormatState(unit, value, options) const parts: string[] = [] if (unit.prefixCode) { if (sign) parts.push(sign) if (code) parts.push(code) parts.push(formattedValue) } else { if (sign) parts.push(sign) parts.push(formattedValue) if (code) { parts.push(nonBreakableSpace) parts.push(code) } } return parts.join(joinFragmentsSeparator) }