# View Module Refactoring Plan

**Inspired by Tempest Framework** - Modernizing the View System with improved syntax and developer experience.

## 🎯 Goals

1. **Modern Placeholder Syntax**: `{{ $variable }}` statt `{variable}` (PHP-like, IDE-friendly)
2. **Component Syntax**: `<x-datatable />` für LiveComponents (Laravel/Tempest-like)
3. **Expression Attributes**: `:attribute` für dynamische Props (Vue/Alpine-like)
4. **View Compilation**: Template-Compilation für Performance
5. **Better DX**: Improved developer experience und tooling support

---

## 📋 Refactoring Phases

### **Phase 1: Placeholder Syntax Migration** 🔄

#### Current State
```html
<!-- Escaped output -->
{{ item.name }}

<!-- Unescaped output -->
{{{ content }}}

<!-- Functions -->
{{ format_date('Y-m-d') }}
```

#### Target State (Tempest-inspired)
```html
<!-- Escaped output with $ prefix -->
{{ $item->name }}

<!-- Unescaped output -->
{!! $content !!}

<!-- Functions (unchanged) -->
{{ format_date('Y-m-d') }}
```

#### Benefits
- **PHP-like syntax**: `$variable` ist vertrauter für PHP-Entwickler
- **IDE Support**: PHPStorm/VSCode erkennen `$` als Variable
- **Clear distinction**: `{{ $var }}` (escaped) vs `{!! $var !!}` (unescaped)
- **Tempest compatibility**: Ähnliche Syntax wie Tempest Framework

#### Implementation Tasks
1. ✅ Update `PlaceholderReplacer` regex patterns
2. ✅ Support both `{{ variable }}` (dotted) and `{{ $variable }}` (PHP-like) syntax
3. ✅ Add `{!! $variable !!}` for unescaped output
4. ✅ Deprecate `{{{ variable }}}` syntax (keep for backward compatibility)
5. ✅ Write tests for new syntax patterns

#### Backward Compatibility Strategy
```php
// Phase 1.1: Support BOTH syntaxes simultaneously
{{ item.name }}        // OLD - still works
{{ $item->name }}      // NEW - preferred

// Phase 1.2: Add deprecation warnings (optional)
// Phase 1.3: Migration script to convert old syntax
// Phase 1.4: Remove old syntax support (major version)
```

---

### **Phase 2: Component Syntax (`<x-*>`)** 🧩

#### Current State
```html
<!-- Verbose include syntax -->
<include template="livecomponent-datatable" data="{id: 'demo', page: 1}" />
```

#### Target State
```html
<!-- Clean component syntax -->
<x-datatable id="demo" page="1" pageSize="25" />
```

#### XComponentProcessor Architecture

```
Input HTML
    ↓
XComponentProcessor (DOM Processor)
    ↓
1. Find all <x-*> elements via XPath
2. Extract component name: "datatable"
3. Extract props: {id: "demo", page: 1, pageSize: 25}
4. Validate props via ComponentMetadata
5. Create ComponentId + ComponentData
6. Resolve via ComponentRegistry
7. Render with LiveComponentRenderer
8. Replace <x-datatable> with rendered HTML
    ↓
Output HTML (with LiveComponent wrapper)
```

#### Implementation Tasks
1. ✅ Create `XComponentProcessor` (implements `DomProcessor`)
2. ✅ XPath-based element discovery: `//*[starts-with(local-name(), 'x-')]`
3. ✅ Prop extraction from HTML attributes
4. ✅ Type coercion: `"123"` → `123`, `"true"` → `true`, `"[1,2,3]"` → `[1,2,3]`
5. ✅ Prop validation via `ComponentMetadataCache`
6. ✅ Integration with existing `ComponentRegistry`
7. ✅ Support for nested components and slots

#### Prop Type Coercion Examples
```html
<x-datatable
    id="users"           <!-- string: "users" -->
    page="1"             <!-- int: 1 -->
    pageSize="25"        <!-- int: 25 -->
    sortAsc="true"       <!-- bool: true -->
    filters='["active"]' <!-- array: ["active"] -->
    meta='{"total":100}' <!-- object: {total: 100} -->
/>
```

```php
// XComponentProcessor::coerceType()
private function coerceType(string $value): mixed
{
    // Boolean
    if ($value === 'true') return true;
    if ($value === 'false') return false;

    // Null
    if ($value === 'null') return null;

    // Numeric
    if (is_numeric($value)) {
        return str_contains($value, '.') ? (float)$value : (int)$value;
    }

    // JSON (arrays/objects)
    if (str_starts_with($value, '[') || str_starts_with($value, '{')) {
        try {
            return json_decode($value, true, 512, JSON_THROW_ON_ERROR);
        } catch (\JsonException) {
            // Fallback to string
        }
    }

    return $value; // String
}
```

#### Prop Validation
```php
// Validate props against ComponentMetadata
private function validateProps(string $componentName, array $props): void
{
    $className = $this->registry->getClassName($componentName);
    $metadata = $this->metadataCache->get($className);

    foreach ($props as $propName => $value) {
        if (!$metadata->hasProperty($propName)) {
            throw new InvalidComponentPropertyException(
                "Component '{$componentName}' has no property '{$propName}'"
            );
        }

        // Optional: Type validation
        $propertyMetadata = $metadata->getProperty($propName);
        $this->validatePropType($propName, $value, $propertyMetadata->type);
    }
}
```

---

### **Phase 3: Expression Attributes (`:attribute`)** ⚡

#### Target State (Tempest-inspired)
```html
<!-- Dynamic attributes with : prefix -->
<x-button
    :disabled="$user->isGuest()"
    :class="$isActive ? 'active' : 'inactive'"
    :data-user-id="$user->id"
>
    Click me
</x-button>

<!-- Mix static and dynamic attributes -->
<x-card
    title="User Profile"              <!-- static -->
    :subtitle="$user->name"           <!-- dynamic -->
    :visible="$user->isActive()"      <!-- dynamic expression -->
/>
```

#### Implementation
```php
// XComponentProcessor enhancement
private function extractProps(HTMLElement $element, RenderContext $context): array
{
    $props = [];

    foreach ($element->attributes as $attr) {
        $name = $attr->nodeName;
        $value = $attr->nodeValue;

        // Expression attribute (starts with :)
        if (str_starts_with($name, ':')) {
            $propName = substr($name, 1); // Remove :
            $props[$propName] = $this->evaluateExpression($value, $context);
        } else {
            // Static attribute
            $props[$name] = $this->coerceType($value);
        }
    }

    return $props;
}

private function evaluateExpression(string $expression, RenderContext $context): mixed
{
    // Simple expression evaluation
    // Could use Symfony Expression Language or simple eval (careful!)

    // For now: Support $variable references and method calls
    if (preg_match('/^\$(\w+)(?:->(\w+)\(\))?$/', $expression, $matches)) {
        $varName = $matches[1];
        $method = $matches[2] ?? null;

        if (isset($context->data[$varName])) {
            $value = $context->data[$varName];

            if ($method && method_exists($value, $method)) {
                return $value->$method();
            }

            return $value;
        }
    }

    // Fallback: return as string
    return $expression;
}
```

---

### **Phase 4: View Compilation** 🚀

#### Current State
- Templates werden bei jedem Request verarbeitet
- String + DOM Processors laufen sequentiell
- Keine Caching der verarbeiteten Templates

#### Target State (Tempest-inspired)
```php
// Compiled view cache
class CompiledViewCache
{
    public function compile(string $templatePath): string
    {
        // 1. Load template
        $html = file_get_contents($templatePath);

        // 2. Process with all processors
        $context = RenderContext::create([]);
        $processed = $this->templateProcessor->render($context, $html);

        // 3. Convert to PHP code
        $phpCode = $this->convertToPhp($processed);

        // 4. Cache compiled template
        $cacheKey = md5($templatePath);
        file_put_contents(
            $this->cacheDir . '/' . $cacheKey . '.php',
            "<?php\n" . $phpCode
        );

        return $phpCode;
    }

    private function convertToPhp(string $html): string
    {
        // Convert {{ $var }} to <?= htmlspecialchars($var) ?>
        // Convert {!! $var !!} to <?= $var ?>
        // Keep static HTML as-is

        $php = preg_replace_callback(
            '/\{\{\s*\$(\w+)\s*\}\}/',
            fn($m) => '<?= htmlspecialchars($' . $m[1] . ') ?>',
            $html
        );

        $php = preg_replace_callback(
            '/\{!!\s*\$(\w+)\s*!!\}/',
            fn($m) => '<?= $' . $m[1] . ' ?>',
            $php
        );

        return $php;
    }
}
```

#### Benefits
- **Performance**: Compiled templates ~10x faster
- **Production optimization**: Skip processing in production
- **Dev mode**: Auto-recompile on changes
- **Debugging**: View compiled PHP code

---

### **Phase 5: Component File Discovery** 📁

#### Target State (Tempest-style)
```
resources/views/
├── components/
│   ├── x-button.view.php        # Auto-discovered
│   ├── x-card.view.php          # Auto-discovered
│   └── x-datatable.view.php     # Auto-discovered
└── pages/
    └── dashboard.view.php
```

#### Auto-Discovery Logic
```php
#[Initializer]
class ComponentDiscoveryInitializer
{
    public function discoverXComponents(): void
    {
        $viewPath = $this->config->get('view.path');
        $componentPath = $viewPath . '/components';

        // Find all x-*.view.php files
        $files = glob($componentPath . '/x-*.view.php');

        foreach ($files as $file) {
            $filename = basename($file);

            // Extract component name: x-button.view.php -> button
            preg_match('/^x-(.+)\.view\.php$/', $filename, $matches);
            $componentName = $matches[1];

            // Register as ViewComponent
            $this->registry->registerFileComponent(
                name: $componentName,
                templatePath: $file
            );
        }
    }
}
```

---

## 🔄 Migration Strategy

### Step 1: Backward Compatibility Phase (v1.x)
- ✅ Support BOTH old and new syntax
- ✅ No breaking changes
- ✅ Add deprecation warnings (optional)

```html
<!-- Both work side-by-side -->
{{ item.name }}          <!-- OLD - deprecated but works -->
{{ $item->name }}        <!-- NEW - preferred -->

<include template="livecomponent-counter" />  <!-- OLD -->
<x-counter />                                  <!-- NEW -->
```

### Step 2: Migration Script
```bash
# Automated migration for templates
php console.php view:migrate-syntax
```

```php
// Migration logic
class SyntaxMigrator
{
    public function migrateTemplate(string $path): void
    {
        $content = file_get_contents($path);

        // 1. Migrate placeholders: {variable} -> {{ $variable }}
        $content = $this->migratePlaceholders($content);

        // 2. Migrate components: <include> -> <x-*>
        $content = $this->migrateComponents($content);

        // 3. Migrate unescaped: {{{ var }}} -> {!! $var !!}
        $content = $this->migrateUnescaped($content);

        file_put_contents($path, $content);
    }

    private function migratePlaceholders(string $html): string
    {
        // {{ item.name }} -> {{ $item->name }}
        return preg_replace_callback(
            '/\{\{\s*([\w.]+)\s*\}\}/',
            function($m) {
                $dotted = $m[1];
                $phpStyle = $this->convertDottedToPhp($dotted);
                return '{{ ' . $phpStyle . ' }}';
            },
            $html
        );
    }

    private function convertDottedToPhp(string $dotted): string
    {
        // item.name -> $item->name
        // user.profile.email -> $user->profile->email
        $parts = explode('.', $dotted);
        return '$' . implode('->', $parts);
    }
}
```

### Step 3: Major Version (v2.0)
- ❌ Remove old syntax support
- ✅ Only new syntax works
- ✅ Breaking change - major version bump

---

## 📊 Comparison: Current vs Target

| Feature | Current | Target | Benefit |
|---------|---------|--------|---------|
| **Placeholder Syntax** | `{variable}` | `{{ $variable }}` | PHP-like, IDE support |
| **Unescaped Output** | `{{{ var }}}` | `{!! $var !!}` | Clearer, Blade-like |
| **Components** | `<include template="..."/>` | `<x-datatable />` | Shorter, cleaner |
| **Dynamic Props** | Not supported | `:disabled="expr"` | Vue/Alpine-like DX |
| **View Compilation** | Runtime only | Compiled + cached | 10x performance |
| **Component Discovery** | Manual registration | Auto-discovery | Less boilerplate |
| **Type Coercion** | Manual | Automatic | Less bugs |
| **Prop Validation** | Runtime errors | Compile-time checks | Better DX |

---

## 🧪 Testing Strategy

### Unit Tests
```php
describe('PlaceholderReplacer', function() {
    it('processes new placeholder syntax', function() {
        $html = '{{ $user->name }}';
        $context = RenderContext::create(['user' => $user]);

        $result = $this->replacer->process($html, $context);

        expect($result)->toBe('John Doe');
    });

    it('processes unescaped output', function() {
        $html = '{!! $htmlContent !!}';
        $context = RenderContext::create(['htmlContent' => '<b>Bold</b>']);

        $result = $this->replacer->process($html, $context);

        expect($result)->toBe('<b>Bold</b>');
    });
});

describe('XComponentProcessor', function() {
    it('processes x-components', function() {
        $html = '<x-datatable id="users" page="1" />';
        $context = RenderContext::create([]);

        $result = $this->processor->process($dom, $context);

        expect($result)->toContain('data-component-id="datatable:users"');
    });

    it('validates component props', function() {
        $html = '<x-datatable invalidProp="test" />';

        expect(fn() => $this->processor->process($dom, $context))
            ->toThrow(InvalidComponentPropertyException::class);
    });
});
```

### Integration Tests
```php
it('renders full page with new syntax', function() {
    $html = <<<HTML
    <h1>{{ $page->title }}</h1>

    <x-datatable
        id="users"
        :page="$currentPage"
        :sortBy="$sortColumn"
    />

    <div>{!! $page->content !!}</div>
    HTML;

    $result = $this->engine->render('dashboard', [
        'page' => $page,
        'currentPage' => 1,
        'sortColumn' => 'name'
    ]);

    expect($result)->toContain('<h1>Dashboard</h1>');
    expect($result)->toContain('data-component-id="datatable:users"');
});
```

---

## 📚 Documentation Updates

### 1. Update Template Syntax Guide
```markdown
# Template Syntax

## Variables

### Escaped Output (Default)
Use `{{ $variable }}` for HTML-escaped output:

{{ $user->name }}
{{ $product->price }}

### Unescaped Output
Use `{!! $variable !!}` for raw HTML:

{!! $page->content !!}
{!! $richText !!}

## Components

### Basic Component
<x-button id="submit">Submit</x-button>

### Component with Props
<x-datatable
    id="users"
    page="1"
    pageSize="25"
/>

### Dynamic Props
<x-card
    :title="$page->title"
    :visible="$user->isAdmin()"
/>
```

### 2. Migration Guide
```markdown
# Migrating to New Syntax

## Automatic Migration

php console.php view:migrate-syntax

## Manual Migration

### Placeholders
Before: {{ user.name }}
After:  {{ $user->name }}

### Unescaped
Before: {{{ content }}}
After:  {!! $content !!}

### Components
Before: <include template="livecomponent-button" />
After:  <x-button />
```

---

## 🎯 Implementation Priority

### **High Priority** (Week 1-2)
1. ✅ Phase 1: Placeholder Syntax (`{{ $variable }}`)
2. ✅ Phase 2: XComponentProcessor (`<x-datatable />`)
3. ✅ Backward compatibility support

### **Medium Priority** (Week 3-4)
4. ✅ Phase 3: Expression Attributes (`:attribute`)
5. ✅ Phase 4: View Compilation
6. ✅ Testing & Documentation

### **Low Priority** (Week 5+)
7. ⏳ Component Auto-Discovery
8. ⏳ Advanced Features (dynamic components, etc.)
9. ⏳ Migration Script
10. ⏳ Remove old syntax (v2.0)

---

## 🚀 Next Steps

**Ready to start?**

1. **Phase 1**: Update `PlaceholderReplacer` for `{{ $variable }}` syntax
2. **Phase 2**: Implement `XComponentProcessor` for `<x-datatable />` syntax
3. **Testing**: Write comprehensive tests
4. **Documentation**: Update guides with examples

**Your Input:**
- Soll ich mit Phase 1 (Placeholder Syntax) oder Phase 2 (XComponentProcessor) starten?
- Backward compatibility: Wie lange sollen wir beide Syntaxen supporten?
- Migration: Automated script oder manual migration?
