import type Big from 'big.js'; import { safeBig } from '../../math/decimal'; const FORMATTER_CACHE = new Map(); const PARTS_CACHE = new Map(); function escapeRegExp(input: string): string { return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); } function resolveParts(locale: string): { decimal: string; group: string } { const cached = PARTS_CACHE.get(locale); if (cached) { return cached; } const parts = new Intl.NumberFormat(locale).formatToParts(12345.6); const decimal = parts.find((part) => part.type === 'decimal')?.value ?? '.'; const group = parts.find((part) => part.type === 'group')?.value ?? ','; const resolved = { decimal, group }; PARTS_CACHE.set(locale, resolved); return resolved; } function normalizeAmountInput(raw: string, locale: string): string { const { decimal, group } = resolveParts(locale); let normalized = raw.trim(); if (normalized === '') { return ''; } normalized = normalized.replace(/[\s\u00A0\u202F\u2009\u2007]/g, ''); if (group) { const groupRegex = new RegExp(escapeRegExp(group), 'g'); normalized = normalized.replace(groupRegex, ''); } if (decimal && decimal !== '.') { const decimalRegex = new RegExp(escapeRegExp(decimal), 'g'); normalized = normalized.replace(decimalRegex, '.'); } normalized = normalized.replace(/[^\d+\-.,]/g, ''); const sign = normalized.startsWith('-') ? '-' : (normalized.startsWith('+') ? '+' : ''); if (sign !== '') { normalized = normalized.slice(1); } normalized = normalized.replace(/[+\-]/g, ''); const lastDot = normalized.lastIndexOf('.'); const lastComma = normalized.lastIndexOf(','); const decimalIndex = Math.max(lastDot, lastComma); if (decimalIndex >= 0) { const intPart = normalized.slice(0, decimalIndex).replace(/[.,]/g, ''); const fracPart = normalized.slice(decimalIndex + 1).replace(/[.,]/g, ''); normalized = `${intPart}.${fracPart}`; } else { normalized = normalized.replace(/[.,]/g, ''); } if (normalized.startsWith('.')) { normalized = `0${normalized}`; } return `${sign}${normalized}`; } export function parseAmount(raw: string, locale: string): Big | null { const cleaned = normalizeAmountInput(raw, locale); if (cleaned === '' || cleaned === '.' || cleaned === '+' || cleaned === '-') return null; try { const parsed = safeBig(cleaned); if (parsed.lt('0')) { return null; } return parsed; } catch { return null; } } function resolveFormatter(locale: string, decimals: number): Intl.NumberFormat { const key = `${locale}:${decimals}`; const cached = FORMATTER_CACHE.get(key); if (cached) { return cached; } const formatter = new Intl.NumberFormat(locale, { minimumFractionDigits: decimals, maximumFractionDigits: decimals, useGrouping: true, }); FORMATTER_CACHE.set(key, formatter); return formatter; } export function formatAmount(value: string | Big, decimals: number, locale: string): string { const fixed = safeBig(value).toFixed(decimals); const numeric = Number.parseFloat(fixed); if (!Number.isFinite(numeric)) { return fixed; } return resolveFormatter(locale, decimals).format(numeric); } export function formatRawAmount(value: string | Big, decimals: number): string { const fixed = safeBig(value).toFixed(decimals); return fixed.replace(/(\.\d*?[1-9])0+$/u, '$1').replace(/\.0+$/u, ''); }