/** * History utility functions. * * Pure functions for parsing and formatting wallet history data. * These functions have no React dependencies and can be used * in both TUI and CLI contexts. */ import type { HistoryItem, HistoryItemType } from '../services/history.js'; /** * Color names for history item types. * These are semantic color names that can be mapped to actual colors * by the consuming application (TUI or CLI). */ export type HistoryColorName = 'info' | 'warning' | 'success' | 'error' | 'muted' | 'text'; /** * Formatted history list item data. */ export interface FormattedHistoryItem { /** The display label for the history item */ label: string; /** Optional secondary description */ description?: string; /** The formatted date string */ dateStr?: string; /** The semantic color name for this item type */ color: HistoryColorName; /** The history item type */ type: HistoryItemType; /** Whether the item data is valid */ isValid: boolean; } /** * Get the semantic color name for a history item type. * * @param type - The history item type * @param isSelected - Whether the item is currently selected * @returns A semantic color name */ export function getHistoryItemColorName(type: HistoryItemType, isSelected: boolean = false): HistoryColorName { if (isSelected) return 'info'; // Use focus color when selected switch (type) { case 'invitation_created': return 'text'; case 'utxo_reserved': return 'warning'; case 'utxo_received': return 'success'; default: return 'text'; } } /** * Format a satoshi value for display. * * @param satoshis - The value in satoshis * @returns Formatted string with BCH amount */ export function formatSatoshisValue(satoshis: bigint | number): string { const value = typeof satoshis === 'bigint' ? satoshis : BigInt(satoshis); const bch = Number(value) / 100_000_000; return `${bch.toFixed(8)} BCH (${value.toLocaleString()} sats)`; } /** * Format a timestamp for display. * * @param timestamp - Unix timestamp in milliseconds * @returns Formatted date string or undefined */ export function formatHistoryDate(timestamp?: number): string | undefined { if (!timestamp) return undefined; return new Date(timestamp).toLocaleDateString(); } /** * Format a history item for display in a list. * * @param item - The history item to format * @param isSelected - Whether the item is currently selected * @returns Formatted item data for display */ export function formatHistoryListItem( item: HistoryItem | null | undefined, isSelected: boolean = false ): FormattedHistoryItem { if (!item) { return { label: '', description: undefined, dateStr: undefined, color: 'muted', type: 'utxo_received', isValid: false, }; } const dateStr = formatHistoryDate(item.timestamp); const color = getHistoryItemColorName(item.type, isSelected); switch (item.type) { case 'invitation_created': return { label: `[Invitation] ${item.description}`, description: undefined, dateStr, color, type: item.type, isValid: true, }; case 'utxo_reserved': { const satsStr = item.valueSatoshis !== undefined ? formatSatoshisValue(item.valueSatoshis) : 'Unknown amount'; return { label: `[Reserved] ${satsStr}`, description: item.description, dateStr, color, type: item.type, isValid: true, }; } case 'utxo_received': { const satsStr = item.valueSatoshis !== undefined ? formatSatoshisValue(item.valueSatoshis) : 'Unknown amount'; const reservedTag = item.reserved ? ' [Reserved]' : ''; return { label: satsStr, description: `${item.description}${reservedTag}`, dateStr, color, type: item.type, isValid: true, }; } default: return { label: `${item.type}: ${item.description}`, description: undefined, dateStr, color: 'text', type: item.type, isValid: true, }; } } /** * Get a type label for display. * * @param type - The history item type * @returns Human-readable type label */ export function getHistoryTypeLabel(type: HistoryItemType): string { switch (type) { case 'invitation_created': return 'Invitation'; case 'utxo_reserved': return 'Reserved'; case 'utxo_received': return 'Received'; default: return type; } } /** * Calculate scrolling window indices for a list. * * @param selectedIndex - Currently selected index * @param totalItems - Total number of items * @param maxVisible - Maximum visible items * @returns Start and end indices for the visible window */ export function calculateScrollWindow( selectedIndex: number, totalItems: number, maxVisible: number ): { startIndex: number; endIndex: number } { const halfWindow = Math.floor(maxVisible / 2); let startIndex = Math.max(0, selectedIndex - halfWindow); const endIndex = Math.min(totalItems, startIndex + maxVisible); // Adjust start if we're near the end if (endIndex - startIndex < maxVisible) { startIndex = Math.max(0, endIndex - maxVisible); } return { startIndex, endIndex }; } /** * Check if a history item is a UTXO-related event. * * @param item - The history item to check * @returns True if the item is UTXO-related */ export function isUtxoEvent(item: HistoryItem): boolean { return item.type === 'utxo_received' || item.type === 'utxo_reserved'; } /** * Filter history items by type. * * @param items - Array of history items * @param types - Types to include * @returns Filtered array */ export function filterHistoryByType( items: HistoryItem[], types: HistoryItemType[] ): HistoryItem[] { return items.filter(item => types.includes(item.type)); } /** * Get summary statistics for history items. * * @param items - Array of history items * @returns Summary statistics */ export function getHistorySummary(items: HistoryItem[]): { totalReceived: bigint; totalReserved: bigint; invitationCount: number; utxoCount: number; } { let totalReceived = 0n; let totalReserved = 0n; let invitationCount = 0; let utxoCount = 0; for (const item of items) { switch (item.type) { case 'invitation_created': invitationCount++; break; case 'utxo_reserved': totalReserved += item.valueSatoshis ?? 0n; break; case 'utxo_received': totalReceived += item.valueSatoshis ?? 0n; utxoCount++; break; } } return { totalReceived, totalReserved, invitationCount, utxoCount }; }