# Validation Guide

This document provides a comprehensive guide to all validation capabilities in the @uplink-protocol/form-controller package.

## Table of Contents

1. [Validation Overview](#validation-overview)
2. [Built-in Validators](#built-in-validators)
3. [Dynamic Validators](#dynamic-validators)
4. [Custom Validators](#custom-validators)
5. [Validation Context](#validation-context)
6. [Error Messages](#error-messages)
7. [Multiple Error Collection](#multiple-error-collection)
8. [Enhanced Validation Features](#enhanced-validation-features)
9. [Advanced Validation Scenarios](#advanced-validation-scenarios)

## Validation Overview

The form-controller offers a robust validation system with multiple layers of validation:

- **Field validation**: Applied to individual fields
- **Step validation**: Applied to each form step
- **Form validation**: Applied to the entire form

Validation can be:
- **Declarative**: Defined in field configuration
- **Imperative**: Triggered through validation methods
- **Reactive**: Automatically triggered during navigation/submission

### Validation Interface

```typescript
interface Validation {
  required?: boolean;
  pattern?: RegExp | string;
  minLength?: number;
  maxLength?: number;
  min?: number;
  max?: number;
  custom?: (value: any) => boolean | string;
  
  // Legacy dynamic validator (deprecated)
  dynamicValidator?: string;
  dynamicValidatorParams?: Record<string, any>;
  
  // Enhanced features
  dynamicValidators?: Array<{
    name: string;
    params?: Record<string, any>;
    errorMessage?: string;
  }>;
  
  errorMessage?: string;
  errorMessages?: {
    required?: string;
    pattern?: string;
    minLength?: string;
    maxLength?: string;
    min?: string;
    max?: string;
    email?: string;
    custom?: string;
  };
  collectAllErrors?: boolean;
}
```

### Validation Priority

Validation rules are evaluated in the following order:

1. **Required validation**: Always checked first, regardless of validation order.
2. **Type-specific validations**: Based on field type (email, pattern, length, numeric range, etc.)
3. **Custom validation**: User-defined validation functions.
4. **Dynamic validation**: Context-aware validation using registered validators.

By default, validation stops on the first error encountered. To collect all validation errors, see the [Multiple Error Collection](#multiple-error-collection) section.

## Built-in Validators

### Required Validation

Ensures a field has a value:

```javascript
{
  id: 'username',
  type: 'text',
  label: 'Username',
  required: true // Shorthand for validation: { required: true }
}
```

Or explicitly:

```javascript
{
  id: 'username',
  type: 'text',
  label: 'Username',
  validation: {
    required: true,
    errorMessage: 'Username is required'
  }
}
```

### Pattern Validation

Validates against a regular expression pattern:

```javascript
{
  id: 'email',
  type: 'email',
  label: 'Email Address',
  validation: {
    pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
    errorMessage: 'Please enter a valid email address'
  }
}
```

### Length Validation

Validates text length:

```javascript
{
  id: 'password',
  type: 'password',
  label: 'Password',
  validation: {
    required: true,
    minLength: 8,
    maxLength: 64,
    errorMessage: 'Password must be between 8 and 64 characters'
  }
}
```

### Numeric Range Validation

Validates numeric values within a range:

```javascript
{
  id: 'age',
  type: 'number',
  label: 'Age',
  validation: {
    required: true,
    min: 18,
    max: 120,
    errorMessage: 'Age must be between 18 and 120'
  }
}
```

### Custom Inline Validation

Provides a custom validation function:

```javascript
{
  id: 'zipCode',
  type: 'text',
  label: 'Zip Code',
  validation: {
    custom: (value) => {
      if (!value) return true; // Skip if empty
      const isValid = /^\d{5}(-\d{4})?$/.test(value);
      return isValid ? true : 'Please enter a valid US zip code';
    }
  }
}
```

## Dynamic Validators

Dynamic validators can access the entire form state and react to other field values.

### Built-in Dynamic Validators

#### requiredIf

Makes a field required based on conditions:

```javascript
// Legacy approach (deprecated)
{
  id: 'otherReason',
  type: 'text',
  label: 'Please specify',
  validation: {
    dynamicValidator: 'requiredIf',
    dynamicValidatorParams: {
      condition: 'equals',
      fields: ['reason'],
      value: 'other',
      errorMessage: 'Please specify the reason'
    }
  }
}

// New approach with dynamicValidators array
{
  id: 'otherReason',
  type: 'text',
  label: 'Please specify',
  validation: {
    dynamicValidators: [
      {
        name: 'requiredIf',
        params: {
          condition: 'equals',
          fields: ['reason'],
          value: 'other'
        },
        errorMessage: 'Please specify the reason'
      }
    ]
  }
}
```

Supported conditions:
- `equals`: Required when a specified field equals a specific value
- `notEquals`: Required when a specified field doesn't equal a specific value
- `notEmpty`: Required when specified fields have values

#### equals

Validates that a field equals a specific value or another field's value:

```javascript
// Legacy approach (deprecated)
{
  id: 'confirmPassword',
  type: 'password',
  label: 'Confirm Password',
  validation: {
    dynamicValidator: 'equals',
    dynamicValidatorParams: {
      targetField: 'password',
      errorMessage: 'Passwords do not match'
    }
  }
}

// New approach with dynamicValidators array
{
  id: 'confirmPassword',
  type: 'password',
  label: 'Confirm Password',
  validation: {
    dynamicValidators: [
      {
        name: 'equals',
        params: {
          targetField: 'password'
        },
        errorMessage: 'Passwords do not match'
      }
    ]
  }
}
```

### Multiple Dynamic Validators

You can apply multiple dynamic validators to a single field:

```javascript
{
  id: 'username',
  type: 'text',
  label: 'Username',
  validation: {
    required: true,
    minLength: 3,
    dynamicValidators: [
      {
        name: 'uniqueUsername',
        params: { checkDatabase: true },
        errorMessage: 'This username is already taken'
      },
      {
        name: 'noSpecialChars',
        errorMessage: 'Username cannot contain special characters'
      }
    ],
    collectAllErrors: true
  }
}
```

### Custom Dynamic Validators

You can create and register your own dynamic validators:
      errorMessage: 'Passwords must match'
    }
  }
}
```

## Custom Validators

### Registering Custom Validators

You can create and register your own dynamic validators:

```javascript
form.methods.registerValidator('matchesPassword', (value, context) => {
  if (!value) return true; // Skip if empty
  
  if (value !== context.formData.password) {
    return 'Passwords do not match';
  }
  
  return true;
});
```

### Using Custom Validators

Once registered, you can use your custom validators in field configuration:

```javascript
{
  id: 'confirmPassword',
  type: 'password',
  label: 'Confirm Password',
  validation: {
    dynamicValidator: 'matchesPassword'
  }
}
```

### Unregistering Validators

You can remove custom validators when no longer needed:

```javascript
form.methods.unregisterValidator('matchesPassword');
```

## Validation Context

Dynamic validators receive a context object with the following properties:

```typescript
interface ValidatorContext {
  field: Field;          // The field configuration
  formData: Record<string, any>; // The entire form data (flattened)
  stepId: string;        // The ID of the current step
  fieldId: string;       // The ID of the field being validated
}
```

This allows validators to:
- Access other field values
- Read field configuration
- Know which step is being validated
- Make context-aware validation decisions

## Error Messages

Error messages can be specified in several ways:

### Default Error Messages

If no custom message is provided, the validator generates a default message.

### Custom Error Messages in Field Configuration

```javascript
{
  id: 'email',
  type: 'email',
  label: 'Email Address',
  validation: {
    pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
    errorMessage: 'Please enter a valid email address'
  }
}
```

### Per-Validation Error Messages

You can specify different error messages for each type of validation:

```javascript
{
  id: 'password',
  type: 'password',
  label: 'Password',
  validation: {
    required: true,
    minLength: 8,
    pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/,
    errorMessages: {
      required: 'Password is required',
      minLength: 'Password must be at least 8 characters long',
      pattern: 'Password must include uppercase, lowercase, and numbers'
    }
  }
}
```

### Error Messages from Validator Functions

```javascript
validation: {
  custom: (value) => {
    if (!value) return true;
    return isValid ? true : 'Custom error message';
  }
}
```

### Error Messages from Dynamic Validators

For legacy dynamic validators:
```javascript
validation: {
  dynamicValidator: 'requiredIf',
  dynamicValidatorParams: {
    // ...parameters
    errorMessage: 'This field is required based on your other selections'
  }
}
```

For new multiple dynamic validators:
```javascript
validation: {
  dynamicValidators: [
    {
      name: 'uniqueUsername',
      params: { checkExisting: true },
      errorMessage: 'This username is already taken'
    }
  ]
}
```

## Multiple Error Collection

By default, validation stops at the first error encountered. However, you can configure fields to collect all validation errors by using the `collectAllErrors` flag:

```javascript
{
  id: 'password',
  type: 'password',
  label: 'Password',
  validation: {
    required: true,
    minLength: 8,
    pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/,
    custom: (value) => {
      if (value.includes('password')) {
        return 'Password should not contain the word "password"';
      }
      return true;
    },
    collectAllErrors: true // Enable collecting all validation errors
  }
}
```

### Benefits of Multiple Error Collection

- **Improved UX**: Users can see all validation issues at once
- **Efficient Form Completion**: Reduces the number of submission attempts
- **Comprehensive Feedback**: Especially useful for complex fields

### Accessing Multiple Errors

When validating a field programmatically, you can access all collected errors:

```javascript
// In custom code that uses the validator directly
const result = validateField(field, value, context);
if (!result.isValid) {
  console.log('First error:', result.error);
  console.log('All errors:', result.errors);
}
```

For more details, see the [Multiple Errors Validation](./multiple-errors-validation.md) documentation.

## Enhanced Validation Features

The form-controller provides enhanced validation capabilities that make it easier to build sophisticated forms with rich validation feedback.

### Per-Validation Error Messages

You can specify different error messages for each validation rule using the `errorMessages` object:

```javascript
{
  id: 'password',
  type: 'password',
  label: 'Password',
  validation: {
    required: true,
    minLength: 8,
    pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/,
    errorMessages: {
      required: 'Password is required',
      minLength: 'Password must be at least 8 characters long',
      pattern: 'Password must include uppercase, lowercase, and numbers'
    }
  }
}
```

The supported error message keys include:
- `required`: For required field validation
- `pattern`: For pattern/regex validation
- `minLength`: For minimum text length validation
- `maxLength`: For maximum text length validation
- `min`: For minimum numeric value validation
- `max`: For maximum numeric value validation
- `email`: For email format validation
- `custom`: For custom validation functions

### Multiple Dynamic Validators

You can apply multiple dynamic validators to a single field:

```javascript
{
  id: 'username',
  type: 'text',
  label: 'Username',
  validation: {
    required: true,
    dynamicValidators: [
      {
        name: 'uniqueUsername',
        params: { checkDatabase: true },
        errorMessage: 'This username is already taken'
      },
      {
        name: 'noSpecialChars',
        errorMessage: 'Username cannot contain special characters'
      }
    ]
  }
}
```

Each validator in the array can have:
- `name`: The registered validator name
- `params`: Custom parameters for the validator
- `errorMessage`: Specific error message for this validator

### Combining Enhanced Features

You can combine all enhanced validation features for maximum flexibility:

```javascript
{
  id: 'password',
  type: 'password',
  label: 'Password',
  validation: {
    required: true,
    minLength: 8,
    pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/,
    
    // Per-validation error messages
    errorMessages: {
      required: 'Password is required',
      minLength: 'Password must be at least 8 characters',
      pattern: 'Password must include uppercase, lowercase, and numbers'
    },
    
    // Multiple dynamic validators with custom parameters
    dynamicValidators: [
      {
        name: 'passwordStrength',
        params: { minStrength: 3 },
        errorMessage: 'Your password is too weak'
      },
      {
        name: 'notPreviouslyUsed',
        params: { historyCheck: true },
        errorMessage: 'You cannot reuse a previous password'
      }
    ],
    
    // Collect all validation errors
    collectAllErrors: true
  }
}
```

See the [Multiple Errors Validation](./multiple-errors-validation.md) document for more examples and best practices.

## Advanced Validation Scenarios

### Asynchronous Validation

You can implement asynchronous validation by combining the form controller with your own async logic:

```javascript
// Field definition
const usernameField = {
  id: 'username',
  type: 'text',
  label: 'Username',
  validation: {
    required: true
  }
};

// Async validation handler
const checkUsernameAvailability = async (username) => {
  // Clear any existing async errors
  form.methods.clearFieldError('account', 'username');
  
  try {
    const response = await fetch(`/api/check-username?username=${username}`);
    const { isAvailable } = await response.json();
    
    if (!isAvailable) {
      form.methods.setFieldError('account', 'username', 'Username already taken');
      return false;
    }
    
    return true;
  } catch (error) {
    console.error('Username validation error:', error);
    form.methods.setFieldError('account', 'username', 'Could not validate username');
    return false;
  }
};

// Connect to UI
usernameInput.addEventListener('blur', async (e) => {
  const username = e.target.value;
  
  // First apply standard validation
  const isValid = form.methods.validateField('account', 'username', username);
  
  // If basic validation passes, check availability
  if (isValid && username) {
    const isAvailable = await checkUsernameAvailability(username);
    // Update UI based on result
  }
});
```

### Cross-Step Validation

You can implement validation that depends on fields in different steps:

```javascript
// Register a validator that checks across steps
form.methods.registerValidator('uniqueInList', (value, context) => {
  if (!value) return true;
  
  // Get items from another step
  const existingItems = context.formData.itemList || [];
  
  // Skip validation for the current item being edited
  const currentItemId = context.formData.currentItemId;
  
  const isDuplicate = existingItems.some(item => 
    item.name === value && item.id !== currentItemId
  );
  
  return isDuplicate ? 'This name already exists in your list' : true;
});
```

### Conditional Field Requirements

Make fields conditionally required based on complex logic:

```javascript
// Payment method scenario
{
  id: 'cardNumber',
  type: 'text',
  label: 'Card Number',
  validation: {
    dynamicValidator: 'requiredIf',
    dynamicValidatorParams: {
      condition: 'equals',
      fields: ['paymentMethod'],
      value: 'credit-card',
      errorMessage: 'Card number is required for credit card payments'
    }
  }
}
```

### Form-Level Validation

Implement validation at the form level:

```javascript
const submitForm = () => {
  // First validate all fields
  const isValid = form.methods.validateForm(true);
  
  if (isValid) {
    const formData = form.methods.getFlatData();
    
    // Perform form-level validation
    if (formData.endDate && formData.startDate) {
      const startDate = new Date(formData.startDate);
      const endDate = new Date(formData.endDate);
      
      if (endDate < startDate) {
        // Set a form-level error
        setFormError('End date must be after start date');
        return false;
      }
    }
    
    // Submit the data
    submitToServer(formData);
  }
};
```

### Complex Validation Rules

For very complex validation rules, you can create a dedicated validator:

```javascript
form.methods.registerValidator('passwordStrength', (value, context) => {
  if (!value) return true;
  
  let strength = 0;
  let feedback = '';
  
  // Length check
  if (value.length >= 8) strength += 1;
  
  // Complexity checks
  if (/[A-Z]/.test(value)) strength += 1;
  if (/[a-z]/.test(value)) strength += 1;
  if (/[0-9]/.test(value)) strength += 1;
  if (/[^A-Za-z0-9]/.test(value)) strength += 1;
  
  // Custom password policy
  if (value.toLowerCase().includes(context.formData.username?.toLowerCase())) {
    feedback = 'Password cannot contain your username';
    strength = 0;
  }
  
  // Return feedback based on strength
  switch (strength) {
    case 0:
    case 1:
      return feedback || 'Password is very weak';
    case 2:
      return feedback || 'Password is weak';
    case 3:
      return true; // Acceptable
    case 4:
    case 5:
      return true; // Strong
  }
});
```

This validation guide provides a comprehensive overview of all validation capabilities in the form-controller package.
