
# Fusion au niveau des cellules

La fusion au niveau des colonnes détecte les modifications simultanées d'une même colonne. La fusion au niveau des cellules va plus loin et fusionne le contenu de cette colonne. Deux personnes modifiant des lignes différentes d'un champ TEXT, ou des clés différentes d'un objet JSON, peuvent fusionner leurs modifications sans conflit.

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

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

## Activer la fusion au niveau des cellules

La fusion au niveau des cellules est facultative. Elle ne s'exécute que dans un `ThreeWayMerge` construit avec un `CellMergeConfig`. Sans cela, toute colonne modifiée par les deux côtés avec des valeurs différentes constitue un conflit `update_update`.

La façade `Merql` n'accepte pas de configuration : `Merql::merge()`, `Merql::patch()` et `Merql::plan()` construisent `new ThreeWayMerge()` sans configuration, et la commande `merge` de CLI passe par `Merql::merge()`. La fusion se fait toujours au niveau des colonnes ([#7](https://github.com/inline0/merql/issues/7)).

Pour fusionner des instantanés enregistrés par la façade ou par CLI avec la fusion au niveau des cellules, chargez-les et appelez vous-même `ThreeWayMerge`. Le résultat est un `MergeResult` ordinaire, que la façade peut toujours appliquer, et `MergePlanBuilder` le transforme en [plan de fusion](https://merql.inline0.dev/fr/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);
```

Les stratégies de fusion des cellules ne s'exécutent que pour les colonnes modifiées par les deux côtés. Si un seul côté a modifié une colonne, sa valeur est reprise telle quelle.

## CellMergeConfig

`CellMergeConfig` associe des colonnes ou des types de colonnes à des implémentations de stratégies de fusion des cellules. La fabrique `auto()` fournit des valeurs par défaut adaptées :

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

Ordre de priorité des stratégies de fusion :

1. Nom de colonne qualifié (`table.column`).
2. Nom de colonne non qualifié (`column`).
3. Correspondance de motif de type de colonne (`str_contains` sans distinction de casse).
4. Valeur par défaut : `OpaqueCellMerger` (signale toujours un conflit lorsque les valeurs diffèrent).

## TextCellMerger

Fusionne le texte sur plusieurs lignes à l'aide du diff de Myers, le même algorithme que git utilise pour le contenu des fichiers. Chaque ligne est traitée comme une unité et les modifications portant sur des lignes différentes fusionnent sans conflit.

```php
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();
```

### Exemple : fusion de texte sans conflit

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

Des lignes différentes ont été modifiées. La fusion produit `"LINE ONE\nline two\nLINE THREE"` sans conflit.

### Exemple : conflit de texte

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

Les deux côtés ont modifié la même ligne avec des valeurs différentes. La stratégie de fusion renvoie un résultat en conflit. Le contenu fusionné inclut des marqueurs de conflit similaires à ceux de git.

### Fonctionnement

`TextCellMerger` délègue à `ThreeWayMerge::merge()` de pitmaster, qui calcule un diff de Myers entre la base et chaque côté, puis combine les deux scripts de modification. Si des modifications se chevauchent sur une même zone de lignes, un conflit est signalé.

## JsonCellMerger

Fusionne les objets JSON en comparant séparément les clés de premier niveau. Chaque clé est fusionnée selon les règles standard à trois voies.

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
```

### Exemple : fusion JSON sans conflit

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

Des clés différentes ont été modifiées. Voici le résultat de la fusion :

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

### Exemple : conflit JSON

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

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

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

Les deux côtés ont modifié la clé `color` avec des valeurs différentes. Il s'agit d'un conflit. Par défaut, la stratégie de fusion conserve notre valeur et signale un conflit.

### Règles au niveau des clés

| Base | Nous | Eux | Résultat |
|---|---|---|---|
| Clé inchangée | Clé inchangée | Accepter la base |
| Clé inchangée | Clé modifiée | Accepter leur valeur |
| Clé modifiée | Clé inchangée | Accepter notre valeur |
| Clé modifiée (identique) | Clé modifiée (identique) | Accepter (accord) |
| Clé modifiée (différente) | Clé modifiée (différente) | **Conflit** (par défaut : notre valeur) |
| Clé présente | Clé présente | Clé supprimée | Accepter la suppression |
| Clé présente | Clé supprimée | Clé présente | Accepter la suppression |
| Clé absente | Clé ajoutée | Clé absente | Accepter l'ajout |
| Clé absente | Clé absente | Clé ajoutée | Accepter l'ajout |

Les objets imbriqués sont comparés comme des chaînes JSON opaques, sans récursion. Si un objet imbriqué diffère, la valeur entière de sa clé est comparée, et non ses sous-clés.

### Prérequis

Les trois valeurs doivent être des objets ou tableaux JSON valides. Si une valeur n'est pas un JSON valide ou est un scalaire JSON (chaîne, nombre, booléen), la stratégie de fusion signale un conflit.

## OpaqueCellMerger

La stratégie de fusion par défaut lorsqu'aucune stratégie au niveau des cellules n'est configurée. Elle traite la valeur de la colonne comme un bloc opaque et signale toujours un conflit lorsque les deux côtés diffèrent.

```php
use Merql\CellMerge\OpaqueCellMerger;

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

## CellMergeResult

Toutes les stratégies de fusion des cellules renvoient un `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
```

Méthodes de fabrique :

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

## Stratégies de fusion des cellules personnalisées

Implémentez l'interface `CellMerger` pour ajouter votre propre stratégie de fusion :

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