
# Merge auf Zellenebene

Beim Merge auf Spaltenebene wird erkannt, wenn beide Seiten dieselbe Spalte geändert haben. Das Merge auf Zellenebene geht tiefer und führt die Inhalte innerhalb dieser Spalte zusammen. Zwei Personen können unterschiedliche Zeilen eines TEXT-Felds oder unterschiedliche Schlüssel desselben JSON-Objekts konfliktfrei bearbeiten.

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;

$config = CellMergeConfig::auto();
$merge = new ThreeWayMerge($config);
$result = $merge->merge($base, $ours, $theirs);
```

## Merge auf Zellenebene aktivieren

Das Merge auf Zellenebene ist optional. Es wird nur in einem `ThreeWayMerge` ausgeführt, das mit einem `CellMergeConfig` erstellt wurde. Ohne dieses Element führt jede Spalte, die beide Seiten auf unterschiedliche Werte geändert haben, zu einem `update_update`-Konflikt.

Die Fassade `Merql` nimmt keine Konfiguration entgegen: `Merql::merge()`, `Merql::patch()` und `Merql::plan()` erstellen `new ThreeWayMerge()` ohne eine solche, und der Befehl `merge` von CLI läuft über `Merql::merge()`. Sie führen immer nur auf Spaltenebene zusammen ([#7](https://github.com/inline0/merql/issues/7)).

Um Snapshots, die mit der Fassade oder CLI erstellt wurden, mit einem Merge auf Zellenebene zusammenzuführen, lade sie und rufe selbst `ThreeWayMerge` auf. Das Ergebnis ist ein gewöhnliches `MergeResult`. Die Fassade kann es also weiterhin anwenden, und `MergePlanBuilder` wandelt es in einen [Merge-Plan](https://merql.inline0.dev/de/docs/merge/plans) um:

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\Merge\ThreeWayMerge;
use Merql\Merql;
use Merql\Plan\MergePlanBuilder;
use Merql\Snapshot\SnapshotStore;

$base   = SnapshotStore::load('base');
$ours   = SnapshotStore::load('ours');
$theirs = SnapshotStore::load('theirs');

$result = (new ThreeWayMerge(CellMergeConfig::auto()))->merge($base, $ours, $theirs);

$plan = (new MergePlanBuilder())->build('plan-1', $base, $ours, $theirs, $result);
```

Zellen-Merger werden nur für Spalten ausgeführt, die beide Seiten geändert haben. Hat nur eine Seite eine Spalte geändert, wird ihr Wert unverändert übernommen.

## CellMergeConfig

`CellMergeConfig` ordnet Spalten oder Spaltentypen passende Zellen-Merger-Implementierungen zu. Die Factory `auto()` stellt sinnvolle Standardwerte bereit:

```php
use Merql\CellMerge\CellMergeConfig;
use Merql\CellMerge\JsonCellMerger;
use Merql\CellMerge\TextCellMerger;

// Auto: column types containing "text" (TEXT, MEDIUMTEXT, LONGTEXT, ...) use
// TextCellMerger, column types containing "json" use JsonCellMerger.
$config = CellMergeConfig::auto();

// Custom: assign mergers to specific columns.
$config = (new CellMergeConfig())
    ->forColumn('posts.content', new TextCellMerger())
    ->forColumn('settings', new JsonCellMerger())
    ->forType('json', new JsonCellMerger());
```

Priorität bei der Auswahl des Mergers:

1. Vollständiger Spaltenname (`table.column`).
2. Spaltenname ohne Qualifizierer (`column`).
3. Abgleich mit Spaltentyp (ohne Beachtung der Groß- und Kleinschreibung: `str_contains`).
4. Standard: `OpaqueCellMerger` (meldet bei unterschiedlichen Werten immer einen Konflikt).

## TextCellMerger

Führt mehrzeiligen Text mit dem Myers-Diff zusammen, demselben Algorithmus, den git für Datei-Inhalte verwendet. Jede Zeile gilt als Einheit; Änderungen an unterschiedlichen Zeilen lassen sich konfliktfrei zusammenführen.

```php
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();
```

### Beispiel: konfliktfreies Text-Merge

```
Base:    "line one\nline two\nline three"
Ours:    "LINE ONE\nline two\nline three"    // changed line 1
Theirs:  "line one\nline two\nLINE THREE"    // changed line 3
```

Es wurden unterschiedliche Zeilen bearbeitet. Das Merge-Ergebnis ist `"LINE ONE\nline two\nLINE THREE"` ohne Konflikt.

### Beispiel: Textkonflikt

```
Base:    "line one\nline two"
Ours:    "changed by us\nline two"     // changed line 1
Theirs:  "changed by them\nline two"   // also changed line 1
```

Beide Seiten haben dieselbe Zeile auf unterschiedliche Werte geändert. Der Merger gibt ein Konfliktergebnis zurück. Der zusammengeführte Inhalt enthält konfliktähnliche Markierungen wie bei git.

### Funktionsweise

`TextCellMerger` delegiert die Aufgabe an `ThreeWayMerge::merge()` von pitmaster. Dieser berechnet einen Myers-Diff zwischen Base und jeder Seite und führt anschließend die beiden Bearbeitungsskripte zusammen. Überschneiden sich Änderungen im selben Zeilenbereich, wird ein Konflikt gemeldet.

## JsonCellMerger

Führt JSON-Objekte zusammen, indem die Schlüssel der obersten Ebene einzeln verglichen werden. Für jeden Schlüssel gelten die üblichen Drei-Wege-Regeln.

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
```

### Beispiel: konfliktfreies JSON-Merge

```json
// Base
{"name": "Alice", "role": "editor", "active": true}

// Ours: changed "role"
{"name": "Alice", "role": "admin", "active": true}

// Theirs: changed "name"
{"name": "Bob", "role": "editor", "active": true}
```

Die Schlüssel wurden unterschiedlich geändert. Das Merge-Ergebnis lautet:

```json
{"name": "Bob", "role": "admin", "active": true}
```

### Beispiel: JSON-Konflikt

```json
// Base
{"color": "red"}

// Ours
{"color": "blue"}

// Theirs
{"color": "green"}
```

Beide Seiten haben den Schlüssel `color` auf unterschiedliche Werte geändert. Das ist ein Konflikt. Standardmäßig übernimmt der Merger Ours und meldet einen Konflikt.

### Regeln auf Schlüsselebene

| Basis | Ours | Theirs | Ergebnis |
|---|---|---|---|
| Schlüssel unverändert | Schlüssel unverändert | Basis übernehmen |
| Schlüssel unverändert | Schlüssel geändert | Theirs übernehmen |
| Schlüssel geändert | Schlüssel unverändert | Ours übernehmen |
| Schlüssel geändert (gleich) | Schlüssel geändert (gleich) | Übernehmen (übereinstimmend) |
| Schlüssel geändert (unterschiedlich) | Schlüssel geändert (unterschiedlich) | **Konflikt** (Standard: Ours) |
| Schlüssel vorhanden | Schlüssel vorhanden | Schlüssel entfernt | Entfernung übernehmen |
| Schlüssel vorhanden | Schlüssel entfernt | Schlüssel vorhanden | Entfernung übernehmen |
| Schlüssel fehlt | Schlüssel hinzugefügt | Schlüssel fehlt | Hinzufügung übernehmen |
| Schlüssel fehlt | Schlüssel fehlt | Schlüssel hinzugefügt | Hinzufügung übernehmen |

Verschachtelte Objekte werden als undurchsichtige JSON-Zeichenfolgen verglichen, nicht rekursiv. Unterscheidet sich ein verschachteltes Objekt, wird der gesamte Wert dieses Schlüssels verglichen, nicht seine Unterschlüssel.

### Voraussetzungen

Alle drei Werte müssen gültige JSON-Objekte oder Arrays sein. Ist ein Wert kein gültiges JSON oder ein skalarer JSON-Wert (String, Zahl, Boolean), fällt der Merger auf einen Konflikt zurück.

## OpaqueCellMerger

Der Standard-Merger, wenn keine Strategie für die Zellenebene konfiguriert ist. Er behandelt den Spaltenwert als undurchsichtigen Block und meldet immer einen Konflikt, wenn sich die Werte beider Seiten unterscheiden.

```php
use Merql\CellMerge\OpaqueCellMerger;

$merger = new OpaqueCellMerger();
$result = $merger->merge($base, $ours, $theirs);
// $result->clean is always false when $ours !== $theirs
```

## CellMergeResult

Alle Zellen-Merger geben ein `CellMergeResult` zurück:

```php
$result = $merger->merge($base, $ours, $theirs);

$result->clean;     // bool: true if merge resolved cleanly
$result->value;     // mixed: the merged value (or ours on conflict)
$result->conflicts; // int: number of conflicts within the cell
```

Factory-Methoden:

```php
CellMergeResult::resolved($value);           // clean merge
CellMergeResult::conflict($oursValue, $n);   // n conflicts, defaults to ours
```

## Benutzerdefinierte Zellen-Merger

Implementiere die Schnittstelle `CellMerger`, um eine eigene Merge-Strategie hinzuzufügen:

```php
use Merql\CellMerge\CellMerger;
use Merql\CellMerge\CellMergeResult;

class YamlCellMerger implements CellMerger
{
    public function merge(mixed $base, mixed $ours, mixed $theirs): CellMergeResult
    {
        // Parse YAML, merge keys, return result.
        $merged = $this->mergeYaml($base, $ours, $theirs);

        if ($merged['clean']) {
            return CellMergeResult::resolved($merged['value']);
        }

        return CellMergeResult::conflict($ours);
    }
}

$config = (new CellMergeConfig())
    ->forColumn('config_data', new YamlCellMerger());
```
