# Utility Functions

Pure, reusable functions for formatting, validation, data transformation, and error handling.

## Export Conventions

### ⚠️ CRITICAL: errorHandler.ts MUST use default export

```typescript
// ✅ CORRECT - errorHandler.ts with default export
export default function errorHandler(error: Error): string {
  return error.message;
}

// ❌ WRONG - Named export will break existing code
export function errorHandler(error: Error): string {
  return error.message;
}
```

### All Other Utilities: Use Named Exports

```typescript
// ✅ CORRECT - Named exports for other utilities
export function formatCurrency(amount: number): string {
  return `$${amount.toFixed(2)}`;
}

export function validateEmail(email: string): boolean {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}
```

## Common Utility Categories

### 1. Formatting Utilities

```typescript
// src/utils/format.ts

/**
 * Format number as currency
 */
export function formatCurrency(
  amount: number,
  currency = 'USD',
  locale = 'en-US'
): string {
  return new Intl.NumberFormat(locale, {
    style: 'currency',
    currency
  }).format(amount);
}

/**
 * Format date
 */
export function formatDate(
  date: Date | string,
  format: 'short' | 'long' | 'iso' = 'short'
): string {
  const d = typeof date === 'string' ? new Date(date) : date;

  if (format === 'iso') {
    return d.toISOString();
  }

  if (format === 'long') {
    return d.toLocaleDateString('en-US', {
      year: 'numeric',
      month: 'long',
      day: 'numeric'
    });
  }

  return d.toLocaleDateString('en-US');
}

/**
 * Format phone number
 */
export function formatPhone(phone: string): string {
  const cleaned = phone.replace(/\D/g, '');

  if (cleaned.length === 10) {
    return `(${cleaned.slice(0, 3)}) ${cleaned.slice(3, 6)}-${cleaned.slice(6)}`;
  }

  return phone;
}

/**
 * Format percentage
 */
export function formatPercent(value: number, decimals = 2): string {
  return `${(value * 100).toFixed(decimals)}%`;
}

/**
 * Format file size
 */
export function formatFileSize(bytes: number): string {
  if (bytes === 0) return '0 Bytes';

  const k = 1024;
  const sizes = ['Bytes', 'KB', 'MB', 'GB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));

  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
}
```

### 2. Validation Utilities

```typescript
// src/utils/validation.ts

/**
 * Validate email format
 */
export function validateEmail(email: string): boolean {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

/**
 * Validate phone number (US format)
 */
export function validatePhone(phone: string): boolean {
  const cleaned = phone.replace(/\D/g, '');
  return cleaned.length === 10;
}

/**
 * Validate credit card number (Luhn algorithm)
 */
export function validateCreditCard(cardNumber: string): boolean {
  const cleaned = cardNumber.replace(/\D/g, '');

  if (cleaned.length < 13 || cleaned.length > 19) {
    return false;
  }

  let sum = 0;
  let isEven = false;

  for (let i = cleaned.length - 1; i >= 0; i--) {
    let digit = parseInt(cleaned[i], 10);

    if (isEven) {
      digit *= 2;
      if (digit > 9) {
        digit -= 9;
      }
    }

    sum += digit;
    isEven = !isEven;
  }

  return sum % 10 === 0;
}

/** Validate password strength — checks length>=8, mixed case, digit, special char */
export function validatePasswordStrength(password: string): {
  isValid: boolean; score: number; feedback: string[];
} { /* score 0-4 based on criteria met, isValid when score>=4 */ }

export function validateDateRange(startDate: Date, endDate: Date): boolean {
  return startDate < endDate;
}
```

### 3. Data Transformation Utilities

```typescript
// src/utils/transform.ts
export function objectToQueryString(obj: Record<string, string | number | boolean>): string {
  return new URLSearchParams(Object.entries(obj).map(([k, v]) => [k, String(v)])).toString();
}

export function groupBy<T>(array: T[], key: keyof T): Record<string, T[]> {
  return array.reduce((r, item) => {
    const k = String(item[key]);
    (r[k] ??= []).push(item);
    return r;
  }, {} as Record<string, T[]>);
}

export function unique<T>(array: T[]): T[] { return [...new Set(array)]; }
export function deepClone<T>(obj: T): T { return JSON.parse(JSON.stringify(obj)); }

export function omit<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: K[]): Omit<T, K> {
  const result = { ...obj }; keys.forEach((k) => delete result[k]); return result;
}

export function pick<T extends Record<string, unknown>, K extends keyof T>(obj: T, keys: K[]): Pick<T, K> {
  const result = {} as Pick<T, K>; keys.forEach((k) => { if (k in obj) result[k] = obj[k]; }); return result;
}
```

### 4. Error Handling Utilities

```typescript
// src/utils/errorHandler.ts

/**
 * ⚠️ CRITICAL: MUST use default export
 * This is the ONLY utility that uses default export
 */

interface ErrorResponse {
  message: string;
  code: string;
  userMessage: string;
}

export default function errorHandler(error: unknown): ErrorResponse {
  // Handle API errors
  if (error && typeof error === 'object' && 'response' in error) {
    const apiError = error as { response: { data: { message: string; code: string } } };

    return {
      message: apiError.response.data.message,
      code: apiError.response.data.code,
      userMessage: getUserFriendlyMessage(apiError.response.data.code)
    };
  }

  // Handle network errors
  if (error instanceof Error && error.message === 'Network Error') {
    return {
      message: 'Network Error',
      code: 'NETWORK_ERROR',
      userMessage: 'Unable to connect to the server. Please check your internet connection.'
    };
  }

  // Handle generic errors
  if (error instanceof Error) {
    return {
      message: error.message,
      code: 'UNKNOWN_ERROR',
      userMessage: 'An unexpected error occurred. Please try again.'
    };
  }

  // Handle unknown errors
  return {
    message: 'Unknown error',
    code: 'UNKNOWN_ERROR',
    userMessage: 'An unexpected error occurred. Please try again.'
  };
}

function getUserFriendlyMessage(code: string): string {
  const messages: Record<string, string> = {
    UNAUTHORIZED: 'Your session has expired. Please log in again.',
    FORBIDDEN: 'You do not have permission to perform this action.',
    NOT_FOUND: 'The requested resource was not found.',
    VALIDATION_ERROR: 'Please check your input and try again.',
    INTERNAL_ERROR: 'A server error occurred. Please try again later.',
    NETWORK_ERROR: 'Unable to connect to the server. Please check your internet connection.'
  };

  return messages[code] || 'An unexpected error occurred. Please try again.';
}

// Usage
import errorHandler from '@/utils/errorHandler';

try {
  await policyRepository.getById('123');
} catch (error) {
  const errorResponse = errorHandler(error);
  toast.error(errorResponse.userMessage);
}
```

### 5. Async Utilities

```typescript
// src/utils/async.ts

/**
 * Delay execution
 */
export function delay(ms: number): Promise<void> {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

/**
 * Retry failed promises
 */
export async function retry<T>(
  fn: () => Promise<T>,
  maxAttempts = 3,
  delayMs = 1000
): Promise<T> {
  let lastError: Error;

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (error) {
      lastError = error as Error;

      if (attempt < maxAttempts) {
        await delay(delayMs * attempt);
      }
    }
  }

  throw lastError!;
}

/**
 * Debounce function calls
 */
export function debounce<T extends (...args: unknown[]) => unknown>(
  fn: T,
  ms: number
): (...args: Parameters<T>) => void {
  let timeoutId: ReturnType<typeof setTimeout>;

  return function (...args: Parameters<T>) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => fn(...args), ms);
  };
}

/**
 * Throttle function calls
 */
export function throttle<T extends (...args: unknown[]) => unknown>(
  fn: T,
  ms: number
): (...args: Parameters<T>) => void {
  let lastRun = 0;

  return function (...args: Parameters<T>) {
    const now = Date.now();

    if (now - lastRun >= ms) {
      fn(...args);
      lastRun = now;
    }
  };
}
```

### 6. String Utilities

```typescript
// src/utils/string.ts
export function capitalize(str: string): string { return str.charAt(0).toUpperCase() + str.slice(1); }
export function toTitleCase(str: string): string { return str.toLowerCase().split(' ').map(capitalize).join(' '); }
export function truncate(str: string, maxLength: number): string { return str.length <= maxLength ? str : `${str.slice(0, maxLength)}...`; }
export function normalizeWhitespace(str: string): string { return str.replace(/\s+/g, ' ').trim(); }
export function slugify(str: string): string { return str.toLowerCase().replace(/[^\w\s-]/g, '').replace(/\s+/g, '-').trim(); }
```

### 7. Number Utilities

```typescript
// src/utils/number.ts
export function clamp(value: number, min: number, max: number): number { return Math.min(Math.max(value, min), max); }
export function round(value: number, decimals: number): number { return Math.round(value * 10 ** decimals) / 10 ** decimals; }
export function percentage(value: number, total: number): number { return total === 0 ? 0 : (value / total) * 100; }
export function inRange(value: number, min: number, max: number): boolean { return value >= min && value <= max; }
```

## Testing Utilities

Test each utility with edge cases. Example:

```typescript
describe('formatCurrency', () => {
  it('formats USD', () => expect(formatCurrency(1234.56)).toBe('$1,234.56'));
  it('handles zero', () => expect(formatCurrency(0)).toBe('$0.00'));
  it('handles negative', () => expect(formatCurrency(-1234.56)).toBe('-$1,234.56'));
});

describe('validateEmail', () => {
  it('validates correct', () => expect(validateEmail('test@example.com')).toBe(true));
  it('rejects invalid', () => expect(validateEmail('invalid')).toBe(false));
});
```

## Best Practices

1. **Keep functions pure** — no side effects or external state mutation
2. **Use TypeScript** — type all parameters and return values
3. **Document complex logic** — JSDoc with `@example` for non-obvious functions
4. **Handle edge cases** — division by zero, null/undefined, empty arrays
5. **Use descriptive names** — `calculateMonthlyPayment()` not `calc()`

## Common Mistakes

### ❌ Mistake 1: Wrong Export for errorHandler

```typescript
// ❌ WRONG - Named export will break existing imports
export function errorHandler(error: Error): string {
  return error.message;
}

// ✅ CORRECT - Default export required
export default function errorHandler(error: Error): string {
  return error.message;
}
```

### ❌ Mistake 2: Mutating Input Parameters

```typescript
// ❌ WRONG - Mutates input array
export function addItem<T>(array: T[], item: T): T[] {
  array.push(item);
  return array;
}

// ✅ CORRECT - Returns new array
export function addItem<T>(array: T[], item: T): T[] {
  return [...array, item];
}
```

### ❌ Mistake 3: Not Handling Null/Undefined

```typescript
// ❌ WRONG - Crashes on null/undefined
export function formatName(firstName: string, lastName: string): string {
  return `${firstName} ${lastName}`;
}

// ✅ CORRECT - Handles null/undefined
export function formatName(firstName?: string, lastName?: string): string {
  const parts = [firstName, lastName].filter(Boolean);
  return parts.join(' ');
}
```

## File Structure

```
src/
└── utils/
    ├── format.ts                   # Formatting utilities
    ├── validation.ts               # Validation utilities
    ├── transform.ts                # Data transformation
    ├── errorHandler.ts             # Error handling (default export!)
    ├── async.ts                    # Async utilities
    ├── string.ts                   # String utilities
    ├── number.ts                   # Number utilities
    └── __tests__/
        ├── format.test.ts
        ├── validation.test.ts
        └── errorHandler.test.ts
```

