# LiveComponents Error Recovery Strategy

## Error Categories

### 1. Component Errors
- Unknown component name
- Invalid component ID format
- Component instantiation failure
- Missing dependencies

### 2. Action Errors
- Unknown action method
- Action method not callable
- Invalid action parameters
- Action execution exception

### 3. State Errors
- Invalid state format
- State deserialization failure
- State validation error
- State version conflict

### 4. Rendering Errors
- Template not found
- Template syntax error
- Rendering exception
- Cache write failure

### 5. Upload Errors
- File validation failure
- Upload size limit exceeded
- Invalid file type
- Storage write failure

### 6. Lifecycle Errors
- onMount() exception
- onUpdate() exception
- onDestroy() exception

## Error Handling Strategy

### Server-Side Error Handling

#### 1. Component Resolution Errors

**Current Behavior:**
```php
public function resolve(ComponentId $componentId, ?ComponentData $state = null): LiveComponentContract
{
    if (!$className) {
        throw new \InvalidArgumentException("Unknown component: {$componentName}");
    }
}
```

**Improved Error Recovery:**
```php
use App\Framework\Exception\FrameworkException;
use App\Framework\Exception\ErrorCode;

public function resolve(ComponentId $componentId, ?ComponentData $state = null): LiveComponentContract
{
    [$componentName, $instanceId] = $this->parseComponentId($componentId->toString());

    $className = $this->nameToClassMap[$componentName] ?? null;

    if (!$className) {
        // Find similar component names (Levenshtein distance)
        $suggestions = $this->findSimilarComponents($componentName);

        throw FrameworkException::create(
            ErrorCode::COMPONENT_NOT_FOUND,
            "Unknown component: '{$componentName}'"
        )->withData([
            'component_name' => $componentName,
            'instance_id' => $instanceId,
            'suggestions' => $suggestions,
            'available_components' => array_keys($this->nameToClassMap)
        ]);
    }

    try {
        return $this->container->invoker->make($className, [
            'id' => $componentId,
            'initialData' => $state
        ]);
    } catch (\Throwable $e) {
        throw FrameworkException::create(
            ErrorCode::COMPONENT_INSTANTIATION_FAILED,
            "Failed to instantiate component '{$componentName}'"
        )->withData([
            'component_class' => $className,
            'error' => $e->getMessage()
        ])->withPrevious($e);
    }
}

private function findSimilarComponents(string $componentName): array
{
    $suggestions = [];

    foreach (array_keys($this->nameToClassMap) as $availableName) {
        $distance = levenshtein($componentName, $availableName);

        if ($distance <= 3) {  // Max 3 character difference
            $suggestions[] = $availableName;
        }
    }

    return $suggestions;
}
```

#### 2. Action Execution Errors

**Enhanced LiveComponentHandler:**
```php
public function handle(
    LiveComponentContract $component,
    string $actionName,
    ActionParameters $params
): ComponentUpdate {
    // Validate action exists
    if (!method_exists($component, $actionName)) {
        $availableActions = $this->getAvailableActions($component);

        throw FrameworkException::create(
            ErrorCode::ACTION_NOT_FOUND,
            "Unknown action '{$actionName}' on component " . get_class($component)
        )->withData([
            'action_name' => $actionName,
            'available_actions' => $availableActions,
            'component_class' => get_class($component)
        ]);
    }

    // Validate action is public and non-static
    $reflection = new \ReflectionMethod($component, $actionName);

    if (!$reflection->isPublic() || $reflection->isStatic()) {
        throw FrameworkException::create(
            ErrorCode::ACTION_NOT_CALLABLE,
            "Action '{$actionName}' is not a public instance method"
        )->withData([
            'action_name' => $actionName,
            'is_public' => $reflection->isPublic(),
            'is_static' => $reflection->isStatic()
        ]);
    }

    // Execute with error recovery
    try {
        $result = $this->container->invoker->invoke($component, $actionName, $params->toArray());

        // Validate result type
        if (!$result instanceof ComponentData) {
            throw FrameworkException::create(
                ErrorCode::ACTION_INVALID_RETURN,
                "Action '{$actionName}' must return ComponentData"
            )->withData([
                'action_name' => $actionName,
                'returned_type' => get_debug_type($result)
            ]);
        }

        return ComponentUpdate::fromActionResult($component, $result);

    } catch (FrameworkException $e) {
        // Re-throw framework exceptions
        throw $e;
    } catch (\Throwable $e) {
        // Wrap other exceptions
        throw FrameworkException::create(
            ErrorCode::ACTION_EXECUTION_FAILED,
            "Action '{$actionName}' execution failed"
        )->withData([
            'action_name' => $actionName,
            'component_class' => get_class($component),
            'error_message' => $e->getMessage(),
            'error_type' => get_class($e)
        ])->withPrevious($e);
    }
}

private function getAvailableActions(LiveComponentContract $component): array
{
    $reflection = new \ReflectionClass($component);
    $actions = [];

    foreach ($reflection->getMethods(\ReflectionMethod::IS_PUBLIC) as $method) {
        if ($method->isStatic() || $method->isConstructor()) {
            continue;
        }

        // Skip getter/setter/lifecycle methods
        if (in_array($method->getName(), ['getId', 'getData', 'getRenderData', 'onMount', 'onUpdate', 'onDestroy'])) {
            continue;
        }

        // Check return type
        $returnType = $method->getReturnType();
        if ($returnType instanceof \ReflectionNamedType &&
            $returnType->getName() === ComponentData::class) {
            $actions[] = $method->getName();
        }
    }

    return $actions;
}
```

#### 3. State Deserialization Errors

**Safe State Handling:**
```php
#[Route('/live-component/{id}', method: Method::POST)]
public function handleAction(string $id, HttpRequest $request): JsonResult
{
    try {
        $action = ComponentAction::fromRequest($request, $id);
    } catch (\Throwable $e) {
        return new JsonResult([
            'success' => false,
            'error' => 'Invalid action format',
            'details' => $e->getMessage()
        ], Status::BAD_REQUEST);
    }

    // Safe state deserialization
    try {
        $stateArray = $request->parsedBody?->get('state', []);

        if (!is_array($stateArray)) {
            throw new \InvalidArgumentException('State must be an array');
        }

        $state = ComponentData::fromArray($stateArray);
    } catch (\Throwable $e) {
        return new JsonResult([
            'success' => false,
            'error' => 'Invalid state format',
            'details' => $e->getMessage(),
            'recovery' => 'Client should reload component from server'
        ], Status::BAD_REQUEST);
    }

    // Component resolution with error recovery
    try {
        $component = $this->componentRegistry->resolve($action->componentId, $state);
    } catch (FrameworkException $e) {
        return new JsonResult([
            'success' => false,
            'error' => $e->getMessage(),
            'data' => $e->getData(),
            'recovery' => 'Check component name and reload page'
        ], Status::NOT_FOUND);
    }

    // Action handling with error recovery
    try {
        $update = $this->handler->handle($component, $action->method, $action->params);
    } catch (FrameworkException $e) {
        return new JsonResult([
            'success' => false,
            'error' => $e->getMessage(),
            'data' => $e->getData(),
            'recovery' => 'Try again or reload component'
        ], Status::UNPROCESSABLE_ENTITY);
    } catch (\Throwable $e) {
        // Unexpected errors
        error_log("LiveComponent action error: " . $e->getMessage());

        return new JsonResult([
            'success' => false,
            'error' => 'Internal server error',
            'recovery' => 'Please reload the page',
            'error_id' => uniqid('lc_error_')  // For support
        ], Status::INTERNAL_SERVER_ERROR);
    }

    // Rendering with error recovery
    try {
        $updatedComponent = $this->componentRegistry->resolve(
            $action->componentId,
            ComponentData::fromArray($update->state->data)
        );

        $html = $this->componentRegistry->render($updatedComponent);

        return new JsonResult([
            'success' => true,
            'html' => $html,
            'state' => $update->state->toArray(),
            'events' => $update->events
        ]);
    } catch (\Throwable $e) {
        error_log("LiveComponent render error: " . $e->getMessage());

        return new JsonResult([
            'success' => false,
            'error' => 'Component rendering failed',
            'state' => $update->state->toArray(),  // Return state so client can retry
            'recovery' => 'Try again',
            'error_id' => uniqid('lc_render_')
        ], Status::INTERNAL_SERVER_ERROR);
    }
}
```

#### 4. Lifecycle Hook Errors

**Safe Lifecycle Hook Execution:**
```php
// In LiveComponentHandler
public function callMountHook(LiveComponentContract $component): void
{
    if (!$component instanceof LifecycleAware) {
        return;
    }

    try {
        $component->onMount();
    } catch (\Throwable $e) {
        // Log but don't fail - lifecycle hooks are side effects only
        error_log(sprintf(
            "Lifecycle hook onMount() failed for %s: %s",
            get_class($component),
            $e->getMessage()
        ));

        // Optional: Dispatch error event for monitoring
        $this->eventDispatcher?->dispatch(
            new ComponentLifecycleErrorEvent(
                componentId: $component->getId(),
                hook: 'onMount',
                error: $e
            )
        );
    }
}

private function callUpdateHook(LiveComponentContract $component): void
{
    if (!$component instanceof LifecycleAware) {
        return;
    }

    try {
        $component->onUpdate();
    } catch (\Throwable $e) {
        error_log(sprintf(
            "Lifecycle hook onUpdate() failed for %s: %s",
            get_class($component),
            $e->getMessage()
        ));
    }
}
```

### Client-Side Error Recovery

#### JavaScript Error Handling

**Enhanced LiveComponents Client:**
```javascript
class LiveComponentManager {
    async handleAction(componentId, actionName, params = {}) {
        const component = this.components.get(componentId);
        if (!component) {
            console.error(`Component not found: ${componentId}`);
            return;
        }

        // Show loading state
        component.element.classList.add('lc-loading');

        try {
            const response = await this.sendAction(componentId, actionName, params);

            if (!response.success) {
                this.handleError(component, response);
                return;
            }

            // Update component
            this.updateComponent(component, response);

        } catch (error) {
            this.handleNetworkError(component, error);
        } finally {
            component.element.classList.remove('lc-loading');
        }
    }

    handleError(component, response) {
        const { error, recovery, data } = response;

        console.error(`Component error: ${error}`, data);

        // Show user-friendly error message
        this.showErrorMessage(component.element, error);

        // Implement recovery strategy
        switch (recovery) {
            case 'Try again':
                this.showRetryButton(component);
                break;

            case 'Client should reload component from server':
                this.reloadComponent(component);
                break;

            case 'Check component name and reload page':
                this.showReloadPageMessage(component);
                break;

            default:
                // Unknown recovery - show generic error
                this.showGenericError(component);
        }

        // Dispatch error event for custom handling
        component.element.dispatchEvent(new CustomEvent('lc:error', {
            detail: { error, recovery, data },
            bubbles: true
        }));
    }

    handleNetworkError(component, error) {
        console.error('Network error:', error);

        // Retry logic with exponential backoff
        if (component.retryCount < 3) {
            const delay = Math.pow(2, component.retryCount) * 1000;
            component.retryCount++;

            setTimeout(() => {
                console.log(`Retrying action (attempt ${component.retryCount})...`);
                // Retry last action
                this.handleAction(
                    component.id,
                    component.lastAction,
                    component.lastParams
                );
            }, delay);
        } else {
            this.showErrorMessage(
                component.element,
                'Network error. Please check your connection and reload the page.'
            );
        }
    }

    reloadComponent(component) {
        // Reload component from server with current state
        fetch(`/live-component/${component.id}/reload`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ state: component.state })
        })
        .then(response => response.json())
        .then(data => {
            if (data.success) {
                this.updateComponent(component, data);
            } else {
                this.showReloadPageMessage(component);
            }
        })
        .catch(() => {
            this.showReloadPageMessage(component);
        });
    }

    showRetryButton(component) {
        const retryHtml = `
            <div class="lc-error-retry">
                <p>Action failed. Would you like to try again?</p>
                <button onclick="this.closest('.lc-error-retry').remove();
                                 window.liveComponents.retry('${component.id}')">
                    Retry
                </button>
            </div>
        `;

        component.element.insertAdjacentHTML('beforeend', retryHtml);
    }

    retry(componentId) {
        const component = this.components.get(componentId);
        if (component && component.lastAction) {
            this.handleAction(
                component.id,
                component.lastAction,
                component.lastParams
            );
        }
    }
}
```

## Error Codes

### Component Errors (COMP_*)
- `COMP_NOT_FOUND` - Component name not registered
- `COMP_INSTANTIATION_FAILED` - Failed to create component instance
- `COMP_INVALID_ID` - Invalid component ID format

### Action Errors (ACTION_*)
- `ACTION_NOT_FOUND` - Action method does not exist
- `ACTION_NOT_CALLABLE` - Action is not public or is static
- `ACTION_INVALID_PARAMS` - Invalid action parameters
- `ACTION_EXECUTION_FAILED` - Action threw exception
- `ACTION_INVALID_RETURN` - Action did not return ComponentData

### State Errors (STATE_*)
- `STATE_INVALID_FORMAT` - State is not valid JSON/array
- `STATE_DESERIALIZATION_FAILED` - Failed to parse state
- `STATE_VALIDATION_FAILED` - State validation error
- `STATE_VERSION_CONFLICT` - Optimistic locking conflict

### Rendering Errors (RENDER_*)
- `RENDER_TEMPLATE_NOT_FOUND` - Template file does not exist
- `RENDER_TEMPLATE_SYNTAX` - Template has syntax error
- `RENDER_EXCEPTION` - Rendering threw exception
- `RENDER_CACHE_WRITE_FAILED` - Failed to write to cache

## Testing Error Recovery

### Unit Tests
```php
it('suggests similar component names on not found error', function () {
    $registry = new ComponentRegistry(...);

    try {
        $registry->resolve(ComponentId::fromString('conter:test'), null);
        expect(false)->toBeTrue(); // Should have thrown
    } catch (FrameworkException $e) {
        expect($e->getData()['suggestions'])->toContain('counter');
    }
});

it('lists available actions on unknown action error', function () {
    $handler = new LiveComponentHandler(...);
    $component = new CounterComponent(...);

    try {
        $handler->handle($component, 'unknownAction', ActionParameters::empty());
        expect(false)->toBeTrue();
    } catch (FrameworkException $e) {
        expect($e->getData()['available_actions'])->toContain('increment');
        expect($e->getData()['available_actions'])->toContain('decrement');
    }
});
```

### Integration Tests
```php
it('recovers from state deserialization error', function () {
    $response = $this->post('/live-component/counter:test', [
        'action' => 'increment',
        'state' => 'invalid json'  // Invalid state
    ]);

    expect($response->status)->toBe(Status::BAD_REQUEST);
    expect($response->json('error'))->toBe('Invalid state format');
    expect($response->json('recovery'))->toContain('reload');
});

it('returns state on rendering error', function () {
    // Component that fails rendering but action succeeded
    $response = $this->post('/live-component/broken:test', [
        'action' => 'increment',
        'state' => ['count' => 5]
    ]);

    expect($response->json('success'))->toBeFalse();
    expect($response->json('state'))->toBeArray();
    expect($response->json('recovery'))->toBe('Try again');
});
```

## Monitoring

### Error Metrics
- `livecomponent.error.rate` - Errors per second by type
- `livecomponent.error.component` - Errors by component name
- `livecomponent.error.action` - Errors by action name
- `livecomponent.error.recovery_success_rate` - Recovery success rate

### Alerts
- Error rate > 5% - Warning
- Error rate > 10% - Critical
- Specific component error rate > 20% - Component issue
- Network error rate > 10% - Infrastructure issue

## Summary

✅ **Implemented:**
- Component not found suggestions
- Available actions listing
- Safe state deserialization
- Lifecycle hook error handling
- Client-side retry logic

⚠️ **To Implement:**
- Error code enum (FrameworkException integration)
- Client-side error recovery UI
- Comprehensive error testing
- Error monitoring dashboard

📋 **Phase 1 Requirements:**
- Implement all error codes
- Add client-side error boundaries
- Comprehensive error tests
- Error monitoring integration
