FoundryKit

Common Patterns

Common patterns and best practices for using FoundryKit utilities

Common Patterns

Learn common patterns and techniques for using FoundryKit utilities effectively in real-world applications.

Component Styling Patterns

Conditional Styling with cn

Use cn for dynamic component styling based on props and state.

import React from 'react'
import { cn } from '@foundrykit/utils'

interface ButtonProps {
  variant?: 'primary' | 'secondary' | 'danger'
  size?: 'sm' | 'md' | 'lg'
  disabled?: boolean
  loading?: boolean
  className?: string
  children: React.ReactNode
}

export function Button({
  variant = 'primary',
  size = 'md',
  disabled = false,
  loading = false,
  className,
  children,
  ...props
}: ButtonProps) {
  return (
    <button
      className={cn(
        // Base styles
        'inline-flex items-center justify-center rounded-md font-medium transition-colors',
        'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2',
        
        // Size variants
        {
          'px-2 py-1 text-xs': size === 'sm',
          'px-4 py-2 text-sm': size === 'md',
          'px-6 py-3 text-base': size === 'lg',
        },
        
        // Color variants
        {
          'bg-blue-600 text-white hover:bg-blue-700 focus-visible:ring-blue-500': variant === 'primary',
          'bg-gray-200 text-gray-900 hover:bg-gray-300 focus-visible:ring-gray-500': variant === 'secondary',
          'bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-500': variant === 'danger',
        },
        
        // State modifiers
        {
          'opacity-50 cursor-not-allowed': disabled || loading,
          'cursor-wait': loading,
        },
        
        // Allow external className override
        className
      )}
      disabled={disabled || loading}
      {...props}
    >
      {loading && (
        <svg className="animate-spin -ml-1 mr-2 h-4 w-4" viewBox="0 0 24 24">
          <circle
            className="opacity-25"
            cx="12"
            cy="12"
            r="10"
            stroke="currentColor"
            strokeWidth="4"
            fill="none"
          />
          <path
            className="opacity-75"
            fill="currentColor"
            d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
          />
        </svg>
      )}
      {children}
    </button>
  )
}

Theme-Based Styling

Create theme-aware components using utility functions.

import React, { createContext, useContext } from 'react'
import { cn, deepMerge } from '@foundrykit/utils'

// Theme configuration
const defaultTheme = {
  colors: {
    primary: 'blue',
    secondary: 'gray',
    danger: 'red',
    success: 'green',
  },
  spacing: {
    sm: '0.5rem',
    md: '1rem',
    lg: '1.5rem',
  },
  borderRadius: {
    sm: '0.25rem',
    md: '0.375rem',
    lg: '0.5rem',
  }
}

type Theme = typeof defaultTheme

const ThemeContext = createContext<Theme>(defaultTheme)

export function ThemeProvider({ 
  theme = {}, 
  children 
}: { 
  theme?: Partial<Theme>
  children: React.ReactNode 
}) {
  const mergedTheme = deepMerge(defaultTheme, theme)
  
  return (
    <ThemeContext.Provider value={mergedTheme}>
      {children}
    </ThemeContext.Provider>
  )
}

export function useTheme() {
  return useContext(ThemeContext)
}

// Theme-aware component
interface ThemedCardProps {
  variant?: keyof Theme['colors']
  size?: keyof Theme['spacing']
  className?: string
  children: React.ReactNode
}

export function ThemedCard({ 
  variant = 'primary', 
  size = 'md',
  className,
  children 
}: ThemedCardProps) {
  const theme = useTheme()
  
  return (
    <div
      className={cn(
        'border rounded shadow-sm',
        `border-${theme.colors[variant]}-200`,
        `p-${size}`,
        `rounded-${size}`,
        className
      )}
    >
      {children}
    </div>
  )
}

Data Processing Patterns

Search and Filter Implementation

Combine multiple utilities for powerful search functionality.

import React, { useState, useMemo } from 'react'
import { debounce, sortBy, groupBy, unique } from '@foundrykit/utils'

interface Product {
  id: string
  name: string
  category: string
  price: number
  tags: string[]
  description: string
}

interface ProductSearchProps {
  products: Product[]
}

export function ProductSearch({ products }: ProductSearchProps) {
  const [query, setQuery] = useState('')
  const [sortField, setSortField] = useState<keyof Product>('name')
  const [selectedCategory, setSelectedCategory] = useState<string>('')
  const [priceRange, setPriceRange] = useState<[number, number]>([0, 1000])

  // Debounced search to avoid excessive filtering
  const debouncedQuery = useMemo(() => {
    let timeoutId: NodeJS.Timeout
    return (value: string) => {
      clearTimeout(timeoutId)
      timeoutId = setTimeout(() => setQuery(value), 300)
    }
  }, [])

  // Get unique categories for filter dropdown
  const categories = useMemo(() => {
    return unique(products.map(p => p.category)).sort()
  }, [products])

  // Filter and sort products
  const filteredProducts = useMemo(() => {
    let filtered = products

    // Text search
    if (query) {
      const searchTerms = query.toLowerCase().split(' ')
      filtered = filtered.filter(product => {
        const searchableText = `${product.name} ${product.description} ${product.tags.join(' ')}`.toLowerCase()
        return searchTerms.every(term => searchableText.includes(term))
      })
    }

    // Category filter
    if (selectedCategory) {
      filtered = filtered.filter(product => product.category === selectedCategory)
    }

    // Price range filter
    filtered = filtered.filter(product => 
      product.price >= priceRange[0] && product.price <= priceRange[1]
    )

    // Sort results
    return sortBy(filtered, sortField)
  }, [products, query, selectedCategory, priceRange, sortField])

  // Group products by category for display
  const groupedProducts = useMemo(() => {
    return groupBy(filteredProducts, 'category')
  }, [filteredProducts])

  return (
    <div className="space-y-6">
      {/* Search Controls */}
      <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
        <input
          type="text"
          placeholder="Search products..."
          onChange={(e) => debouncedQuery(e.target.value)}
          className="px-3 py-2 border rounded-md"
        />
        
        <select
          value={selectedCategory}
          onChange={(e) => setSelectedCategory(e.target.value)}
          className="px-3 py-2 border rounded-md"
        >
          <option value="">All Categories</option>
          {categories.map(category => (
            <option key={category} value={category}>
              {category}
            </option>
          ))}
        </select>
        
        <select
          value={sortField}
          onChange={(e) => setSortField(e.target.value as keyof Product)}
          className="px-3 py-2 border rounded-md"
        >
          <option value="name">Sort by Name</option>
          <option value="price">Sort by Price</option>
          <option value="category">Sort by Category</option>
        </select>

        <div className="flex items-center space-x-2">
          <span className="text-sm">Price:</span>
          <input
            type="range"
            min="0"
            max="1000"
            value={priceRange[1]}
            onChange={(e) => setPriceRange([priceRange[0], parseInt(e.target.value)])}
            className="flex-1"
          />
          <span className="text-sm">${priceRange[1]}</span>
        </div>
      </div>

      {/* Results */}
      <div className="space-y-6">
        {Object.entries(groupedProducts).map(([category, categoryProducts]) => (
          <div key={category}>
            <h2 className="text-xl font-semibold mb-4">{category}</h2>
            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
              {categoryProducts.map(product => (
                <div key={product.id} className="border rounded-lg p-4">
                  <h3 className="font-medium">{product.name}</h3>
                  <p className="text-gray-600 text-sm mt-1">{product.description}</p>
                  <div className="mt-2 flex items-center justify-between">
                    <span className="font-semibold">${product.price}</span>
                    <div className="flex flex-wrap gap-1">
                      {product.tags.map(tag => (
                        <span
                          key={tag}
                          className="px-2 py-1 bg-gray-100 text-xs rounded"
                        >
                          {tag}
                        </span>
                      ))}
                    </div>
                  </div>
                </div>
              ))}
            </div>
          </div>
        ))}
      </div>

      {filteredProducts.length === 0 && (
        <div className="text-center py-8 text-gray-500">
          No products found matching your criteria.
        </div>
      )}
    </div>
  )
}

Data Transformation Pipeline

Create reusable data transformation patterns.

import { pick, omit, groupBy, sortBy, chunk } from '@foundrykit/utils'

interface RawUserData {
  id: string
  firstName: string
  lastName: string
  email: string
  password: string
  role: string
  department: string
  salary: number
  joinDate: string
  isActive: boolean
  permissions: string[]
}

interface PublicUserData {
  id: string
  name: string
  email: string
  role: string
  department: string
  joinDate: string
  isActive: boolean
}

interface UserAnalytics {
  totalUsers: number
  activeUsers: number
  departmentBreakdown: Record<string, number>
  roleDistribution: Record<string, number>
  recentJoins: PublicUserData[]
}

class UserDataProcessor {
  // Transform raw user data to public format
  static toPublicUser(user: RawUserData): PublicUserData {
    return {
      ...omit(user, ['password', 'salary', 'permissions']),
      name: `${user.firstName} ${user.lastName}`
    }
  }

  // Process multiple users
  static toPublicUsers(users: RawUserData[]): PublicUserData[] {
    return users.map(this.toPublicUser)
  }

  // Create user analytics
  static generateAnalytics(users: RawUserData[]): UserAnalytics {
    const publicUsers = this.toPublicUsers(users)
    const activeUsers = publicUsers.filter(user => user.isActive)
    
    // Group by department and role
    const departmentGroups = groupBy(publicUsers, 'department')
    const roleGroups = groupBy(publicUsers, 'role')
    
    // Convert to counts
    const departmentBreakdown = Object.fromEntries(
      Object.entries(departmentGroups).map(([dept, users]) => [dept, users.length])
    )
    
    const roleDistribution = Object.fromEntries(
      Object.entries(roleGroups).map(([role, users]) => [role, users.length])
    )
    
    // Get recent joins (last 30 days, sorted by join date)
    const thirtyDaysAgo = new Date()
    thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30)
    
    const recentJoins = sortBy(
      publicUsers.filter(user => new Date(user.joinDate) >= thirtyDaysAgo),
      'joinDate'
    ).reverse().slice(0, 10) // Latest 10

    return {
      totalUsers: publicUsers.length,
      activeUsers: activeUsers.length,
      departmentBreakdown,
      roleDistribution,
      recentJoins
    }
  }

  // Paginate users
  static paginateUsers(users: PublicUserData[], pageSize: number = 20) {
    const chunks = chunk(users, pageSize)
    
    return {
      pages: chunks,
      totalPages: chunks.length,
      totalUsers: users.length,
      pageSize
    }
  }

  // Export users for different formats
  static exportUsers(users: RawUserData[], format: 'csv' | 'admin' | 'public') {
    switch (format) {
      case 'csv':
        return users.map(user => pick(user, [
          'id', 'firstName', 'lastName', 'email', 'department', 'role'
        ]))
      
      case 'admin':
        return users.map(user => omit(user, ['password']))
      
      case 'public':
        return this.toPublicUsers(users)
      
      default:
        throw new Error(`Unsupported export format: ${format}`)
    }
  }
}

// Usage example
const rawUsers: RawUserData[] = [
  // ... user data
]

// Generate analytics dashboard data
const analytics = UserDataProcessor.generateAnalytics(rawUsers)

// Create paginated user list
const paginatedUsers = UserDataProcessor.paginateUsers(
  UserDataProcessor.toPublicUsers(rawUsers),
  25
)

// Export data in different formats
const csvData = UserDataProcessor.exportUsers(rawUsers, 'csv')
const adminData = UserDataProcessor.exportUsers(rawUsers, 'admin')

Form Handling Patterns

Advanced Form Validation

Combine validation utilities for comprehensive form handling.

import React, { useState, useCallback } from 'react'
import { debounce, isEmail, validateSchema, deepMerge } from '@foundrykit/utils'

interface FormData {
  personalInfo: {
    firstName: string
    lastName: string
    email: string
    phone: string
  }
  address: {
    street: string
    city: string
    zipCode: string
    country: string
  }
  preferences: {
    newsletter: boolean
    notifications: string[]
    theme: 'light' | 'dark'
  }
}

const formSchema = {
  personalInfo: {
    type: 'object',
    properties: {
      firstName: { type: 'string', required: true, minLength: 2 },
      lastName: { type: 'string', required: true, minLength: 2 },
      email: { type: 'string', required: true, validator: isEmail },
      phone: { type: 'string', required: false, pattern: /^\+?[\d\s-()]+$/ }
    }
  },
  address: {
    type: 'object',
    properties: {
      street: { type: 'string', required: true },
      city: { type: 'string', required: true },
      zipCode: { type: 'string', required: true, pattern: /^\d{5}(-\d{4})?$/ },
      country: { type: 'string', required: true }
    }
  },
  preferences: {
    type: 'object',
    properties: {
      newsletter: { type: 'boolean', required: false },
      notifications: { type: 'array', required: false },
      theme: { type: 'string', required: true, enum: ['light', 'dark'] }
    }
  }
}

export function AdvancedForm() {
  const [formData, setFormData] = useState<FormData>({
    personalInfo: {
      firstName: '',
      lastName: '',
      email: '',
      phone: ''
    },
    address: {
      street: '',
      city: '',
      zipCode: '',
      country: ''
    },
    preferences: {
      newsletter: false,
      notifications: [],
      theme: 'light'
    }
  })

  const [errors, setErrors] = useState<Record<string, any>>({})
  const [isValidating, setIsValidating] = useState(false)

  // Debounced validation to avoid excessive API calls
  const debouncedValidation = useCallback(
    debounce(async (data: FormData) => {
      setIsValidating(true)
      
      try {
        const validation = validateSchema(data, formSchema)
        setErrors(validation.errors)
        
        // Additional async validation (e.g., email uniqueness)
        if (validation.isValid && data.personalInfo.email) {
          const emailExists = await checkEmailExists(data.personalInfo.email)
          if (emailExists) {
            setErrors(prev => deepMerge(prev, {
              personalInfo: { email: 'Email already exists' }
            }))
          }
        }
      } finally {
        setIsValidating(false)
      }
    }, 500),
    []
  )

  // Update form data and trigger validation
  const updateFormData = useCallback((updates: Partial<FormData>) => {
    setFormData(prev => {
      const newData = deepMerge(prev, updates)
      debouncedValidation(newData)
      return newData
    })
  }, [debouncedValidation])

  // Helper to update nested form fields
  const updateField = (path: string, value: any) => {
    const pathParts = path.split('.')
    const updates = pathParts.reduceRight((acc, key) => ({ [key]: acc }), value)
    updateFormData(updates)
  }

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    
    const validation = validateSchema(formData, formSchema)
    
    if (!validation.isValid) {
      setErrors(validation.errors)
      return
    }

    try {
      await submitForm(formData)
      // Handle success
    } catch (error) {
      // Handle error
    }
  }

  return (
    <form onSubmit={handleSubmit} className="max-w-2xl mx-auto space-y-8">
      {/* Personal Information Section */}
      <section>
        <h2 className="text-xl font-semibold mb-4">Personal Information</h2>
        <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
          <FormField
            label="First Name"
            value={formData.personalInfo.firstName}
            onChange={(value) => updateField('personalInfo.firstName', value)}
            error={errors.personalInfo?.firstName}
          />
          
          <FormField
            label="Last Name"
            value={formData.personalInfo.lastName}
            onChange={(value) => updateField('personalInfo.lastName', value)}
            error={errors.personalInfo?.lastName}
          />
          
          <FormField
            label="Email"
            type="email"
            value={formData.personalInfo.email}
            onChange={(value) => updateField('personalInfo.email', value)}
            error={errors.personalInfo?.email}
            className="md:col-span-2"
          />
          
          <FormField
            label="Phone (Optional)"
            value={formData.personalInfo.phone}
            onChange={(value) => updateField('personalInfo.phone', value)}
            error={errors.personalInfo?.phone}
            className="md:col-span-2"
          />
        </div>
      </section>

      {/* Address Section */}
      <section>
        <h2 className="text-xl font-semibold mb-4">Address</h2>
        <div className="space-y-4">
          <FormField
            label="Street Address"
            value={formData.address.street}
            onChange={(value) => updateField('address.street', value)}
            error={errors.address?.street}
          />
          
          <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
            <FormField
              label="City"
              value={formData.address.city}
              onChange={(value) => updateField('address.city', value)}
              error={errors.address?.city}
            />
            
            <FormField
              label="ZIP Code"
              value={formData.address.zipCode}
              onChange={(value) => updateField('address.zipCode', value)}
              error={errors.address?.zipCode}
            />
            
            <FormField
              label="Country"
              value={formData.address.country}
              onChange={(value) => updateField('address.country', value)}
              error={errors.address?.country}
            />
          </div>
        </div>
      </section>

      {/* Preferences Section */}
      <section>
        <h2 className="text-xl font-semibold mb-4">Preferences</h2>
        <div className="space-y-4">
          <label className="flex items-center space-x-2">
            <input
              type="checkbox"
              checked={formData.preferences.newsletter}
              onChange={(e) => updateField('preferences.newsletter', e.target.checked)}
            />
            <span>Subscribe to newsletter</span>
          </label>
          
          <div>
            <label className="block text-sm font-medium mb-2">Theme</label>
            <select
              value={formData.preferences.theme}
              onChange={(e) => updateField('preferences.theme', e.target.value)}
              className="w-full px-3 py-2 border rounded-md"
            >
              <option value="light">Light</option>
              <option value="dark">Dark</option>
            </select>
          </div>
        </div>
      </section>

      <div className="flex items-center justify-between">
        <div className="text-sm text-gray-500">
          {isValidating && 'Validating...'}
        </div>
        
        <button
          type="submit"
          disabled={isValidating}
          className="px-6 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 disabled:opacity-50"
        >
          Submit
        </button>
      </div>
    </form>
  )
}

// Helper component for form fields
interface FormFieldProps {
  label: string
  value: string
  onChange: (value: string) => void
  error?: string
  type?: string
  className?: string
}

function FormField({ 
  label, 
  value, 
  onChange, 
  error, 
  type = 'text',
  className 
}: FormFieldProps) {
  return (
    <div className={className}>
      <label className="block text-sm font-medium mb-1">{label}</label>
      <input
        type={type}
        value={value}
        onChange={(e) => onChange(e.target.value)}
        className={cn(
          'w-full px-3 py-2 border rounded-md',
          error && 'border-red-500'
        )}
      />
      {error && (
        <p className="mt-1 text-sm text-red-600">{error}</p>
      )}
    </div>
  )
}

// Mock functions
async function checkEmailExists(email: string): Promise<boolean> {
  // Simulate API call
  return new Promise(resolve => {
    setTimeout(() => resolve(email === 'taken@example.com'), 500)
  })
}

async function submitForm(data: FormData): Promise<void> {
  // Simulate form submission
  return new Promise(resolve => {
    setTimeout(resolve, 1000)
  })
}

Performance Optimization Patterns

Memoization and Caching

Use memoization for expensive computations.

import React, { useMemo } from 'react'
import { memoize, debounce, sortBy, groupBy } from '@foundrykit/utils'

// Expensive calculation that should be memoized
const expensiveCalculation = memoize((data: number[], operation: string) => {
  console.log('Performing expensive calculation...') // Only logs when not cached
  
  switch (operation) {
    case 'sum':
      return data.reduce((sum, num) => sum + num, 0)
    case 'average':
      return data.reduce((sum, num) => sum + num, 0) / data.length
    case 'median':
      const sorted = [...data].sort((a, b) => a - b)
      const mid = Math.floor(sorted.length / 2)
      return sorted.length % 2 === 0 
        ? (sorted[mid - 1] + sorted[mid]) / 2 
        : sorted[mid]
    default:
      return 0
  }
})

// Memoized data processing
const processLargeDataset = memoize((
  dataset: Array<{ id: string; value: number; category: string }>,
  filters: { category?: string; minValue?: number }
) => {
  let processed = dataset

  // Apply filters
  if (filters.category) {
    processed = processed.filter(item => item.category === filters.category)
  }
  
  if (filters.minValue !== undefined) {
    processed = processed.filter(item => item.value >= filters.minValue)
  }

  // Group and sort
  const grouped = groupBy(processed, 'category')
  const sorted = Object.fromEntries(
    Object.entries(grouped).map(([category, items]) => [
      category,
      sortBy(items, 'value').reverse()
    ])
  )

  return {
    processed,
    grouped: sorted,
    summary: {
      total: processed.length,
      categories: Object.keys(sorted).length,
      totalValue: processed.reduce((sum, item) => sum + item.value, 0)
    }
  }
})

interface DataDashboardProps {
  rawData: Array<{ id: string; value: number; category: string }>
}

export function DataDashboard({ rawData }: DataDashboardProps) {
  const [filters, setFilters] = useState({
    category: '',
    minValue: 0
  })

  // Memoize expensive data processing
  const processedData = useMemo(() => {
    return processLargeDataset(rawData, filters)
  }, [rawData, filters])

  // Memoize calculations
  const statistics = useMemo(() => {
    const values = processedData.processed.map(item => item.value)
    
    return {
      sum: expensiveCalculation(values, 'sum'),
      average: expensiveCalculation(values, 'average'),
      median: expensiveCalculation(values, 'median')
    }
  }, [processedData.processed])

  // Debounced filter updates
  const debouncedUpdateFilters = useMemo(
    () => debounce((newFilters: typeof filters) => {
      setFilters(newFilters)
    }, 300),
    []
  )

  return (
    <div className="space-y-6">
      {/* Filters */}
      <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
        <select
          value={filters.category}
          onChange={(e) => debouncedUpdateFilters({ ...filters, category: e.target.value })}
          className="px-3 py-2 border rounded-md"
        >
          <option value="">All Categories</option>
          {Object.keys(processedData.grouped).map(category => (
            <option key={category} value={category}>{category}</option>
          ))}
        </select>
        
        <input
          type="number"
          placeholder="Minimum value"
          onChange={(e) => debouncedUpdateFilters({ 
            ...filters, 
            minValue: parseInt(e.target.value) || 0 
          })}
          className="px-3 py-2 border rounded-md"
        />
      </div>

      {/* Summary Statistics */}
      <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
        <div className="bg-blue-50 p-4 rounded-lg">
          <h3 className="font-semibold text-blue-900">Total Items</h3>
          <p className="text-2xl font-bold text-blue-600">{processedData.summary.total}</p>
        </div>
        
        <div className="bg-green-50 p-4 rounded-lg">
          <h3 className="font-semibold text-green-900">Sum</h3>
          <p className="text-2xl font-bold text-green-600">{statistics.sum.toLocaleString()}</p>
        </div>
        
        <div className="bg-yellow-50 p-4 rounded-lg">
          <h3 className="font-semibold text-yellow-900">Average</h3>
          <p className="text-2xl font-bold text-yellow-600">{statistics.average.toFixed(2)}</p>
        </div>
        
        <div className="bg-purple-50 p-4 rounded-lg">
          <h3 className="font-semibold text-purple-900">Median</h3>
          <p className="text-2xl font-bold text-purple-600">{statistics.median.toFixed(2)}</p>
        </div>
      </div>

      {/* Grouped Data Display */}
      <div className="space-y-4">
        {Object.entries(processedData.grouped).map(([category, items]) => (
          <div key={category} className="border rounded-lg p-4">
            <h3 className="font-semibold mb-2">{category}</h3>
            <div className="grid grid-cols-1 md:grid-cols-3 gap-2">
              {items.slice(0, 6).map(item => (
                <div key={item.id} className="flex justify-between p-2 bg-gray-50 rounded">
                  <span className="truncate">{item.id}</span>
                  <span className="font-medium">{item.value}</span>
                </div>
              ))}
            </div>
            {items.length > 6 && (
              <p className="text-sm text-gray-500 mt-2">
                +{items.length - 6} more items
              </p>
            )}
          </div>
        ))}
      </div>
    </div>
  )
}

Error Handling Patterns

Robust Error Handling with Utilities

Create comprehensive error handling patterns.

import { debounce, isEmail, validateSchema } from '@foundrykit/utils'

// Error types
interface ValidationError {
  type: 'validation'
  field: string
  message: string
}

interface NetworkError {
  type: 'network'
  status?: number
  message: string
}

interface UnknownError {
  type: 'unknown'
  message: string
  originalError?: Error
}

type AppError = ValidationError | NetworkError | UnknownError

// Error handling utilities
class ErrorHandler {
  private static errorLog: AppError[] = []

  static logError(error: AppError) {
    this.errorLog.push(error)
    console.error('Application Error:', error)
  }

  static createValidationError(field: string, message: string): ValidationError {
    return { type: 'validation', field, message }
  }

  static createNetworkError(status: number, message: string): NetworkError {
    return { type: 'network', status, message }
  }

  static createUnknownError(message: string, originalError?: Error): UnknownError {
    return { type: 'unknown', message, originalError }
  }

  static getErrorMessage(error: AppError): string {
    switch (error.type) {
      case 'validation':
        return `${error.field}: ${error.message}`
      case 'network':
        return `Network error${error.status ? ` (${error.status})` : ''}: ${error.message}`
      case 'unknown':
        return `Unexpected error: ${error.message}`
    }
  }

  static getErrorLog(): AppError[] {
    return [...this.errorLog]
  }

  static clearErrorLog() {
    this.errorLog = []
  }
}

// Async operation with error handling
async function safeApiCall<T>(
  apiCall: () => Promise<T>,
  retries: number = 3,
  delay: number = 1000
): Promise<{ data?: T; error?: AppError }> {
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      const data = await apiCall()
      return { data }
    } catch (error) {
      if (attempt === retries) {
        const appError = error instanceof Error
          ? ErrorHandler.createUnknownError(error.message, error)
          : ErrorHandler.createUnknownError('Unknown error occurred')
        
        ErrorHandler.logError(appError)
        return { error: appError }
      }
      
      // Wait before retry
      await new Promise(resolve => setTimeout(resolve, delay * attempt))
    }
  }
  
  return { error: ErrorHandler.createUnknownError('Max retries exceeded') }
}

// Form with comprehensive error handling
interface ContactFormData {
  name: string
  email: string
  message: string
}

const contactFormSchema = {
  name: { type: 'string', required: true, minLength: 2 },
  email: { type: 'string', required: true, validator: isEmail },
  message: { type: 'string', required: true, minLength: 10 }
}

export function ContactForm() {
  const [formData, setFormData] = useState<ContactFormData>({
    name: '',
    email: '',
    message: ''
  })
  
  const [errors, setErrors] = useState<Record<string, string>>({})
  const [isSubmitting, setIsSubmitting] = useState(false)
  const [submitError, setSubmitError] = useState<AppError | null>(null)
  const [submitSuccess, setSubmitSuccess] = useState(false)

  // Debounced validation
  const debouncedValidation = useMemo(
    () => debounce((data: ContactFormData) => {
      const validation = validateSchema(data, contactFormSchema)
      const fieldErrors: Record<string, string> = {}
      
      Object.entries(validation.errors).forEach(([field, error]) => {
        if (error) {
          fieldErrors[field] = error
          ErrorHandler.logError(ErrorHandler.createValidationError(field, error))
        }
      })
      
      setErrors(fieldErrors)
    }, 300),
    []
  )

  const handleInputChange = (field: keyof ContactFormData, value: string) => {
    const newData = { ...formData, [field]: value }
    setFormData(newData)
    debouncedValidation(newData)
    
    // Clear submit error when user starts typing
    if (submitError) {
      setSubmitError(null)
    }
  }

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault()
    
    // Validate form
    const validation = validateSchema(formData, contactFormSchema)
    if (!validation.isValid) {
      const fieldErrors: Record<string, string> = {}
      Object.entries(validation.errors).forEach(([field, error]) => {
        if (error) fieldErrors[field] = error
      })
      setErrors(fieldErrors)
      return
    }

    setIsSubmitting(true)
    setSubmitError(null)
    setSubmitSuccess(false)

    // Submit with error handling
    const { data, error } = await safeApiCall(async () => {
      const response = await fetch('/api/contact', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(formData)
      })

      if (!response.ok) {
        throw ErrorHandler.createNetworkError(
          response.status,
          `Failed to submit form: ${response.statusText}`
        )
      }

      return response.json()
    })

    setIsSubmitting(false)

    if (error) {
      setSubmitError(error)
    } else {
      setSubmitSuccess(true)
      setFormData({ name: '', email: '', message: '' })
      setErrors({})
    }
  }

  return (
    <div className="max-w-md mx-auto">
      <form onSubmit={handleSubmit} className="space-y-4">
        <div>
          <label className="block text-sm font-medium mb-1">Name</label>
          <input
            type="text"
            value={formData.name}
            onChange={(e) => handleInputChange('name', e.target.value)}
            className={cn(
              'w-full px-3 py-2 border rounded-md',
              errors.name && 'border-red-500'
            )}
          />
          {errors.name && (
            <p className="mt-1 text-sm text-red-600">{errors.name}</p>
          )}
        </div>

        <div>
          <label className="block text-sm font-medium mb-1">Email</label>
          <input
            type="email"
            value={formData.email}
            onChange={(e) => handleInputChange('email', e.target.value)}
            className={cn(
              'w-full px-3 py-2 border rounded-md',
              errors.email && 'border-red-500'
            )}
          />
          {errors.email && (
            <p className="mt-1 text-sm text-red-600">{errors.email}</p>
          )}
        </div>

        <div>
          <label className="block text-sm font-medium mb-1">Message</label>
          <textarea
            rows={4}
            value={formData.message}
            onChange={(e) => handleInputChange('message', e.target.value)}
            className={cn(
              'w-full px-3 py-2 border rounded-md',
              errors.message && 'border-red-500'
            )}
          />
          {errors.message && (
            <p className="mt-1 text-sm text-red-600">{errors.message}</p>
          )}
        </div>

        {submitError && (
          <div className="p-3 bg-red-50 border border-red-200 rounded-md">
            <p className="text-sm text-red-600">
              {ErrorHandler.getErrorMessage(submitError)}
            </p>
          </div>
        )}

        {submitSuccess && (
          <div className="p-3 bg-green-50 border border-green-200 rounded-md">
            <p className="text-sm text-green-600">
              Message sent successfully!
            </p>
          </div>
        )}

        <button
          type="submit"
          disabled={isSubmitting || Object.keys(errors).length > 0}
          className={cn(
            'w-full py-2 px-4 rounded-md font-medium',
            'bg-blue-600 text-white hover:bg-blue-700',
            'disabled:opacity-50 disabled:cursor-not-allowed'
          )}
        >
          {isSubmitting ? 'Sending...' : 'Send Message'}
        </button>
      </form>
    </div>
  )
}

Next Steps