# LiveComponent Lifecycle Hooks

Dokumentation des Lifecycle Hook Systems für LiveComponents.

## Übersicht

Das Lifecycle Hook System bietet opt-in Callbacks für wichtige Lebenszyklus-Ereignisse einer LiveComponent:

- **`onMount()`**: Aufgerufen einmalig nach erster Erstellung (server-side)
- **`onUpdate()`**: Aufgerufen nach jeder State-Änderung (server-side)
- **`onDestroy()`**: Aufgerufen vor Entfernung aus DOM (client-side mit server-call)

## LifecycleAware Interface

Components müssen das `LifecycleAware` Interface implementieren um Lifecycle Hooks zu nutzen:

```php
use App\Framework\LiveComponents\Contracts\LifecycleAware;
use App\Framework\LiveComponents\Contracts\LiveComponentContract;

#[LiveComponent(name: 'example')]
final readonly class ExampleComponent
    implements LiveComponentContract, LifecycleAware
{
    public function onMount(): void
    {
        // Initialization logic
    }

    public function onUpdate(): void
    {
        // React to state changes
    }

    public function onDestroy(): void
    {
        // Cleanup logic
    }
}
```

**Wichtig**: Das Interface ist optional - Components die es nicht implementieren funktionieren weiterhin normal.

## Lifecycle Flow

```
1. Component Creation (Initial Page Load)
   ↓
   onMount() - called once
   ↓
2. User Action (Button Click, Input Change)
   ↓
   Action Execution
   ↓
   State Validation
   ↓
   onUpdate() - called after state change
   ↓
3. Component Removal (Navigate Away, Remove Element)
   ↓
   onDestroy() - called before removal
   ↓
   Client-Side Cleanup
```

## Hook Details

### onMount()

**Wann aufgerufen**: Einmalig nach erster Component-Erstellung (server-side)

**Trigger**: ComponentRegistry ruft Hook auf wenn `$state === null` (initial creation ohne Re-Hydration)

**Use Cases**:
- Timer oder Intervals starten
- Datenbank-Connections öffnen
- Events oder WebSockets subscriben
- Externe Libraries initialisieren
- Component Mount für Analytics loggen
- Background Processes starten

**Beispiel**:
```php
public function onMount(): void
{
    // Log component initialization
    error_log("TimerComponent mounted: {$this->id->toString()}");

    // Initialize external resources
    $this->cache->remember("timer_{$this->id}", fn() => time());

    // Subscribe to events
    $this->eventBus->subscribe('timer:tick', $this->handleTick(...));
}
```

**Wichtig**:
- Wird NUR bei initialer Erstellung aufgerufen (kein `$state` Parameter)
- Wird NICHT aufgerufen bei Re-Hydration mit existierendem State
- Fehler werden geloggt aber brechen Component-Erstellung nicht ab

### onUpdate()

**Wann aufgerufen**: Nach jeder Action die State aktualisiert (server-side)

**Trigger**: LiveComponentHandler ruft Hook nach State-Validierung auf in `handle()` und `handleUpload()` Methoden

**Use Cases**:
- Auf State-Änderungen reagieren
- Externe Ressourcen aktualisieren
- Mit externen Services synchronisieren
- State-Konsistenz validieren
- State-Transitions loggen
- Cache invalidieren

**Beispiel**:
```php
public function onUpdate(): void
{
    $seconds = $this->data->get('seconds', 0);
    $isRunning = $this->data->get('isRunning', false);

    // Log state transitions
    error_log("Timer updated: {$seconds}s, running: " . ($isRunning ? 'yes' : 'no'));

    // Update external resources
    if ($isRunning) {
        $this->cache->set("timer_{$this->id}_last_active", time());
    }

    // Trigger side effects
    if ($seconds >= 60) {
        $this->eventBus->dispatch(new TimerReachedMinuteEvent($this->id));
    }
}
```

**Wichtig**:
- Wird nach JEDER Action aufgerufen (auch wenn State unverändert bleibt)
- Wird nach State-Validierung aufgerufen (State ist garantiert valid)
- Fehler werden geloggt aber brechen Action nicht ab

### onDestroy()

**Wann aufgerufen**: Vor Component-Entfernung aus DOM (client-side mit server-call)

**Trigger**:
1. Client-Side MutationObserver erkennt DOM-Entfernung
2. JavaScript ruft `/live-component/{id}/destroy` Endpunkt auf
3. Server-Side Controller ruft `onDestroy()` Hook auf

**Use Cases**:
- Timers und Intervals stoppen
- Datenbank-Connections schließen
- Events unsubscriben
- Externe Ressourcen aufräumen
- State vor Removal persistieren
- Component-Entfernung loggen

**Beispiel**:
```php
public function onDestroy(): void
{
    // Log component removal
    error_log("TimerComponent destroyed: {$this->id->toString()}");

    // Persist final state
    $this->storage->save("timer_{$this->id}_final_state", $this->data->toArray());

    // Cleanup subscriptions
    $this->eventBus->unsubscribe('timer:tick');

    // Close connections
    $this->connection?->close();
}
```

**Wichtig**:
- Best-effort delivery via `navigator.sendBeacon` oder `fetch`
- Fehler brechen Destroy nicht ab (Component wird trotzdem entfernt)
- Kann fehlschlagen bei Page Unload (Browser beendet Request)
- Nur für kritisches Cleanup verwenden

## Client-Side Integration

### MutationObserver Setup

Der LiveComponentManager JavaScript-Code überwacht automatisch DOM-Entfernungen:

```javascript
setupLifecycleObserver(element, componentId) {
    const observer = new MutationObserver((mutations) => {
        if (!document.contains(element)) {
            this.callDestroyHook(componentId);
            observer.disconnect();
        }
    });

    if (element.parentNode) {
        observer.observe(element.parentNode, {
            childList: true,
            subtree: false
        });
    }

    config.observer = observer;
}
```

### Server-Call mit navigator.sendBeacon

Best-effort delivery auch während Page Unload:

```javascript
async callDestroyHook(componentId) {
    const payload = JSON.stringify({
        state: currentState,
        _csrf_token: csrfToken
    });

    const url = `/live-component/${componentId}/destroy`;
    const blob = new Blob([payload], { type: 'application/json' });

    // Try sendBeacon first for page unload reliability
    if (navigator.sendBeacon && navigator.sendBeacon(url, blob)) {
        console.log(`onDestroy() called via sendBeacon`);
    } else {
        // Fallback to fetch for normal removal
        await fetch(url, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Requested-With': 'XMLHttpRequest'
            },
            body: payload
        });
    }

    // Local cleanup
    this.destroy(componentId);
}
```

## Error Handling

Alle Lifecycle Hooks haben robustes Error Handling:

```php
// In LiveComponentHandler
try {
    $component->onMount();
} catch (\Throwable $e) {
    // Log error but don't fail component creation
    error_log("Lifecycle hook onMount() failed for " . get_class($component) . ": " . $e->getMessage());
}
```

**Best Practices**:
- Hooks sollten nie kritische Exceptions werfen
- Internes Error Handling in Hook-Implementierungen
- Logging für Debugging und Monitoring
- Graceful Degradation bei Hook-Fehlern

## Timer Component Beispiel

Vollständiges Beispiel einer Component mit allen Lifecycle Hooks:

```php
#[LiveComponent(name: 'timer')]
final readonly class TimerComponent
    implements LiveComponentContract, LifecycleAware
{
    private ComponentData $data;

    public function __construct(
        private ComponentId $id,
        ?ComponentData $initialData = null
    ) {
        $this->data = $initialData ?? ComponentData::fromArray([
            'seconds' => 0,
            'isRunning' => false,
            'startedAt' => null,
            'logs' => []
        ]);
    }

    // Lifecycle Hooks

    public function onMount(): void
    {
        error_log("TimerComponent mounted: {$this->id->toString()}");
        $this->addLog('Component mounted - Timer ready');
    }

    public function onUpdate(): void
    {
        $seconds = $this->data->get('seconds', 0);
        $isRunning = $this->data->get('isRunning', false);

        error_log("TimerComponent updated: {$seconds}s, running: " .
                 ($isRunning ? 'yes' : 'no'));
    }

    public function onDestroy(): void
    {
        error_log("TimerComponent destroyed: {$this->id->toString()}");
        $this->addLog('Component destroyed - Cleanup completed');
    }

    // Actions

    public function start(): ComponentData
    {
        $state = $this->data->toArray();
        $state['isRunning'] = true;
        $state['startedAt'] = time();

        $this->addLog('Timer started', $state);

        return ComponentData::fromArray($state);
    }

    public function stop(): ComponentData
    {
        $state = $this->data->toArray();
        $state['isRunning'] = false;

        $this->addLog('Timer stopped', $state);

        return ComponentData::fromArray($state);
    }

    public function tick(): ComponentData
    {
        $state = $this->data->toArray();

        if ($state['isRunning']) {
            $state['seconds'] = ($state['seconds'] ?? 0) + 1;
        }

        return ComponentData::fromArray($state);
    }

    // Standard Interface Methods

    public function getId(): ComponentId
    {
        return $this->id;
    }

    public function getData(): ComponentData
    {
        return $this->data;
    }

    public function getRenderData(): RenderData
    {
        return new RenderData(
            templatePath: 'livecomponent-timer',
            data: [
                'seconds' => $this->data->get('seconds', 0),
                'isRunning' => $this->data->get('isRunning', false),
                'formattedTime' => $this->formatTime($this->data->get('seconds', 0)),
                'logs' => $this->data->get('logs', [])
            ]
        );
    }

    // Helper Methods

    private function formatTime(int $seconds): string
    {
        $minutes = floor($seconds / 60);
        $remainingSeconds = $seconds % 60;

        return sprintf('%02d:%02d', $minutes, $remainingSeconds);
    }

    private function addLog(string $message, array &$state = null): void
    {
        if ($state === null) {
            $state = $this->data->toArray();
        }

        $logs = $state['logs'] ?? [];
        $logs[] = [
            'time' => date('H:i:s'),
            'message' => $message
        ];

        // Keep only last 5 logs
        $state['logs'] = array_slice($logs, -5);
    }
}
```

## Best Practices

### 1. Minimal onMount() Logic

```php
// ✅ Good: Light initialization
public function onMount(): void
{
    error_log("Component {$this->id} mounted");
    $this->cache->set("mounted_{$this->id}", time(), 3600);
}

// ❌ Bad: Heavy computation
public function onMount(): void
{
    $this->processAllData(); // Slow!
    $this->generateReport();  // Blocks creation!
}
```

### 2. onUpdate() Performance

```php
// ✅ Good: Quick updates
public function onUpdate(): void
{
    if ($this->data->get('isActive')) {
        $this->cache->touch("active_{$this->id}");
    }
}

// ❌ Bad: Heavy synchronous operations
public function onUpdate(): void
{
    $this->syncWithExternalAPI();     // Slow!
    $this->recalculateEverything();   // Blocks action!
}
```

### 3. Critical Cleanup in onDestroy()

```php
// ✅ Good: Essential cleanup
public function onDestroy(): void
{
    $this->connection?->close();
    $this->persistState();
}

// ❌ Bad: Nice-to-have cleanup
public function onDestroy(): void
{
    $this->updateStatistics();  // May fail during page unload
    $this->sendAnalytics();     // Not guaranteed to complete
}
```

### 4. Error Handling

```php
// ✅ Good: Internal error handling
public function onMount(): void
{
    try {
        $this->externalService->connect();
    } catch (\Exception $e) {
        error_log("Connection failed: " . $e->getMessage());
        // Continue with degraded functionality
    }
}

// ❌ Bad: Letting exceptions bubble up
public function onMount(): void
{
    $this->externalService->connect(); // May throw!
    // Breaks component creation if it fails
}
```

### 5. Idempotency

Hooks sollten idempotent sein (mehrfach ausführbar ohne Seiteneffekte):

```php
// ✅ Good: Idempotent
public function onMount(): void
{
    if (!$this->cache->has("initialized_{$this->id}")) {
        $this->cache->set("initialized_{$this->id}", true);
        $this->initialize();
    }
}

// ❌ Bad: Side effects on every call
public function onMount(): void
{
    $this->counter++;  // Breaks on re-hydration!
}
```

## Testing Lifecycle Hooks

```php
use Tests\Framework\LiveComponents\ComponentTestCase;

uses(ComponentTestCase::class);

beforeEach(function () {
    $this->setUpComponentTest();
});

it('calls onMount on initial creation', function () {
    $mountCalled = false;

    $component = new class (...) implements LifecycleAware {
        public function onMount(): void {
            $this->mountCalled = true;
        }
    };

    // Initial creation (no state)
    $registry = $this->container->get(ComponentRegistry::class);
    $resolved = $registry->resolve($component->getId(), null);

    expect($mountCalled)->toBeTrue();
});

it('calls onUpdate after action', function () {
    $updateCalled = false;

    $component = new class (...) implements LifecycleAware {
        public function onUpdate(): void {
            $this->updateCalled = true;
        }
    };

    $handler = $this->container->get(LiveComponentHandler::class);
    $params = ActionParameters::fromArray([
        '_csrf_token' => $this->generateCsrfToken($component)
    ]);

    $handler->handle($component, 'action', $params);

    expect($updateCalled)->toBeTrue();
});
```

## Demo

Eine vollständige Demo ist verfügbar unter:
- **URL**: https://localhost/livecomponent-timer
- **Component**: `src/Application/LiveComponents/Timer/TimerComponent.php`
- **Template**: `src/Framework/View/templates/livecomponent-timer.view.php`

Die Demo zeigt:
- Alle drei Lifecycle Hooks in Aktion
- Client-Side Tick Interval Management
- Lifecycle Log-Tracking
- Browser Console Logging für Hook-Aufrufe

## Zusammenfassung

Das Lifecycle Hook System bietet:
- ✅ **Opt-in Design**: Components können hooks nutzen ohne Breaking Changes
- ✅ **Server-Side Hooks**: onMount() und onUpdate() mit voller Backend-Integration
- ✅ **Client-Side Cleanup**: onDestroy() mit MutationObserver und sendBeacon
- ✅ **Robustes Error Handling**: Fehler brechen Lifecycle nicht ab
- ✅ **Best-Effort Delivery**: onDestroy() versucht Server-Call auch bei Page Unload
- ✅ **Framework-Integration**: Nahtlos integriert mit ComponentRegistry und LiveComponentHandler

**Use Cases**:
- Resource Management (Connections, Timers, Subscriptions)
- State Persistence und Synchronization
- Analytics und Logging
- External Service Integration
- Performance Monitoring
