
# Samenvoeging op celniveau

Een samenvoeging op kolomniveau detecteert wanneer beide kanten dezelfde kolom hebben gewijzigd. Een samenvoeging op celniveau gaat dieper en voegt de inhoud binnen die kolom samen. Twee mensen die verschillende regels in een TEXT-veld of verschillende sleutels in een JSON-object bewerken, kunnen hun wijzigingen probleemloos samenvoegen.

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

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

## Samenvoeging op celniveau inschakelen

Samenvoeging op celniveau is optioneel. Deze wordt alleen uitgevoerd in een `ThreeWayMerge` die is samengesteld met een `CellMergeConfig`. Zonder zo'n configuratie levert elke kolom die beide kanten in verschillende waarden hebben gewijzigd een `update_update`-conflict op.

De `Merql`-gevel accepteert geen configuratie: `Merql::merge()`, `Merql::patch()` en `Merql::plan()` maken een `new ThreeWayMerge()` zonder configuratie, en de opdracht `merge` van de CLI loopt via `Merql::merge()`. Ze voegen altijd samen op kolomniveau ([#7](https://github.com/inline0/merql/issues/7)).

Om snapshots die zijn opgeslagen via de gevel of de CLI samen te voegen met samenvoeging op celniveau, laad je ze en roep je zelf `ThreeWayMerge` aan. Het resultaat is een gewone `MergeResult`, dus de gevel kan deze nog steeds toepassen, en `MergePlanBuilder` zet deze om in een [samenvoegplan](https://merql.inline0.dev/nl/docs/merge/plans):

```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);
```

Samenvoegers voor cellen worden alleen uitgevoerd voor kolommen die beide kanten hebben gewijzigd. Als slechts één kant een kolom heeft gewijzigd, wordt de waarde ervan ongewijzigd overgenomen.

## CellMergeConfig

`CellMergeConfig` koppelt kolommen of kolomtypen aan implementaties van samenvoegers voor cellen. De `auto()`-factory biedt redelijke standaardwaarden:

```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());
```

Prioriteit bij het kiezen van een samenvoeger:

1. Volledig gekwalificeerde kolomnaam (`table.column`).
2. Niet-gekwalificeerde kolomnaam (`column`).
3. Kolomtype op basis van een patroon (niet hoofdlettergevoelig `str_contains`).
4. Standaard: `OpaqueCellMerger` (meldt altijd een conflict wanneer waarden verschillen).

## TextCellMerger

Voegt tekst van meerdere regels samen met Myers diff, hetzelfde algoritme dat git gebruikt voor bestandsinhoud. Elke regel wordt als een eenheid behandeld en wijzigingen aan verschillende regels worden probleemloos samengevoegd.

```php
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();
```

### Voorbeeld: tekst probleemloos samenvoegen

```
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
```

Er zijn verschillende regels bewerkt. De samenvoeging levert `"LINE ONE\nline two\nLINE THREE"` op, zonder conflict.

### Voorbeeld: tekstconflict

```
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 kanten hebben dezelfde regel gewijzigd in verschillende waarden. De samenvoeger retourneert een conflicteresultaat. De samengevoegde inhoud bevat conflictmarkeringen die lijken op die van git.

### Werking

`TextCellMerger` gebruikt de `ThreeWayMerge::merge()` van pitmaster, die een Myers diff berekent tussen base en elke kant en vervolgens beide bewerkingsscripts combineert. Als bewerkingen elkaar overlappen in hetzelfde regelgebied, wordt een conflict gemeld.

## JsonCellMerger

Voegt JSON-objecten samen door de sleutels op het hoogste niveau afzonderlijk te vergelijken. Elke sleutel wordt samengevoegd volgens de standaardregels voor drieweg-samenvoeging.

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
```

### Voorbeeld: JSON probleemloos samenvoegen

```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}
```

Er zijn verschillende sleutels gewijzigd. Het samengevoegde resultaat is:

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

### Voorbeeld: JSON-conflict

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

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

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

Beide kanten hebben de `color`-sleutel gewijzigd in verschillende waarden. Dit is een conflict. De samenvoeger kiest standaard ours en meldt één conflict.

### Regels op sleutelniveau

| Basis | Ours | Theirs | Resultaat |
|---|---|---|---|
| Sleutel ongewijzigd | Sleutel ongewijzigd | Basis accepteren |
| Sleutel ongewijzigd | Sleutel gewijzigd | Theirs accepteren |
| Sleutel gewijzigd | Sleutel ongewijzigd | Ours accepteren |
| Sleutel gewijzigd (zelfde) | Sleutel gewijzigd (zelfde) | Accepteren (komen overeen) |
| Sleutel gewijzigd (anders) | Sleutel gewijzigd (anders) | **Conflict** (standaard: ours) |
| Sleutel bestaat | Sleutel bestaat | Sleutel verwijderd | Verwijdering accepteren |
| Sleutel bestaat | Sleutel verwijderd | Sleutel bestaat | Verwijdering accepteren |
| Sleutel ontbreekt | Sleutel toegevoegd | Sleutel ontbreekt | Toevoeging accepteren |
| Sleutel ontbreekt | Sleutel ontbreekt | Sleutel toegevoegd | Toevoeging accepteren |

Geneste objecten worden vergeleken als ondoorzichtige JSON-strings, niet recursief. Als een genest object verschilt, wordt de volledige waarde van die sleutel vergeleken, niet de subsleutels.

### Vereisten

Alle drie de waarden moeten geldige JSON-objecten of arrays zijn. Als een waarde geen geldige JSON is, of een scalaire JSON-waarde is (string, getal, boolean), valt de samenvoeger terug op een conflict.

## OpaqueCellMerger

De standaardmerger wanneer er geen strategie op celniveau is ingesteld. Deze behandelt de kolomwaarde als een ondoorzichtige blob en meldt altijd een conflict wanneer beide kanten verschillen.

```php
use Merql\CellMerge\OpaqueCellMerger;

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

## CellMergeResult

Alle celmergers retourneren een `CellMergeResult`:

```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
```

Factorymethoden:

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

## Aangepaste celmergers

Implementeer de interface `CellMerger` om je eigen samenvoegstrategie toe te voegen:

```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());
```
