import type { CurrInstance, CurrDefinition, CurrDisplay, CurrConfig, ConversionValue } from './curr_types.ts'; import type { LingInstance, LingNode } from '@/ling/lib/types.ts'; import type { LocaleResolver, GlobLocale } from './comn_types.ts'; import { resolveLocale } from './comn_engine.ts'; import { defaultISOLocale } from '@/ling/lib/consts.ts'; /** * Crea un módulo de moneda con formateo y conversión. * * @param definitions - Mapa de monedas disponibles indexado por código ISO. * @param ling - Instancia de ling, usada para la pluralización de nombres. * @param opts - Configuración inicial (moneda activa, decimales, display). * @param defaultLocale - Locale o resolver usado cuando `f()` no recibe locale explícito. */ export function createCurr( definitions : Record, ling : LingInstance, opts : CurrConfig = {}, defaultLocale: LocaleResolver = defaultISOLocale ): CurrInstance { let selectedCurrency: string = opts.selectedCurrency ?? Object.keys(definitions)[0] ?? ''; const baseCurrency : string = opts.baseCurrency ?? selectedCurrency; const defaults = { display : opts.display ?? 'symbol' as CurrDisplay, decimals : opts.decimals ?? 2, }; // ── Currency ────────────────────────────────────────────────────────────── function getCurrency(): string { return selectedCurrency; } function setCurrency(currency: string): void { selectedCurrency = currency.toUpperCase(); } /** Busca una definición por código ISO, insensible a mayúsculas. */ function get(currency: string): CurrDefinition | undefined { return definitions[currency.toUpperCase()]; } // ── Helpers ─────────────────────────────────────────────────────────────── function formatAmount(amount: number, decimals: number, locale: GlobLocale): string { return new Intl.NumberFormat(locale, { minimumFractionDigits: decimals, maximumFractionDigits: decimals, }).format(amount); } /** Resuelve el nombre plural de la moneda según `amount` y el idioma del locale. */ function resolveName(def: CurrDefinition, amount: number, locale: GlobLocale): string { const record = def.name({ count: Math.abs(amount) }); const baseLocale = locale.split('-')[0] as keyof typeof record; return record[baseLocale] ?? record['es']; } /** Normaliza `ConversionValue` a número o `null`. Evalúa la función si es dinámica. */ function resolveConversionValue(value: ConversionValue): number | null { if (value === null || value === undefined) return null; if (typeof value === 'number') return value; return value(); } // ── Format ──────────────────────────────────────────────────────────────── /** * Formatea `amount` en la moneda activa. * Si la moneda no está registrada, devuelve el importe seguido del código ISO. */ function f( amount : number, display : CurrDisplay = defaults.display, locale : LocaleResolver = defaultLocale ): string { const loc = resolveLocale(locale); const formatted = formatAmount(amount, defaults.decimals, loc); const def = get(selectedCurrency); if (!def) return `${formatted} ${selectedCurrency}`; switch (display) { case 'symbol': return `${formatted} ${def.symbol}`; case 'code': return `${formatted} ${def.code}`; case 'name': return `${formatted} ${resolveName(def, amount, loc)}`; } } // ── Conversión ──────────────────────────────────────────────────────────── /** * Convierte `amount` de `from` a `to` usando sus tasas respecto a la moneda base. * Fórmula: `(amount / fromRate) * toRate` * Devuelve `null` si alguna moneda no tiene tasa de conversión. */ function convert(amount: number, from: string, to: string): number | null { if (from.toUpperCase() === to.toUpperCase()) return amount; const fromDef = get(from); const toDef = get(to); if (!fromDef || !toDef) return null; const fromRate = resolveConversionValue(fromDef.conversionValue ?? null); const toRate = resolveConversionValue(toDef.conversionValue ?? null); if (fromRate === null || toRate === null) return null; // Convertir a moneda base y luego a destino const inBase = amount / fromRate; return inBase * toRate; } /** Convierte desde la moneda activa (`selectedCurrency`) hacia `to`. */ function convertTo(amount: number, to: string): number | null { return convert(amount, selectedCurrency, to); } return { f, convert, convertTo, setCurrency, getCurrency, get }; }