
# Gestion des conflits

Un conflit survient lorsque les deux côtés d'une fusion modifient les mêmes données de manière incompatible. merql détecte quatre types de conflits et propose des stratégies de résolution automatiques et manuelles.

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

if (!$result->isClean()) {
    foreach ($result->conflicts() as $conflict) {
        echo "{$conflict->table()}.{$conflict->column()}: "
           . "{$conflict->oursValue()} vs {$conflict->theirsValue()}\n";
    }
}
```

## Types de conflit

### update_update

Les deux côtés ont modifié la même colonne de la même ligne avec des valeurs différentes. C'est le type de conflit le plus courant.

```
Base:    posts #42  title = "Hello"
Ours:    posts #42  title = "Welcome"
Theirs:  posts #42  title = "Greetings"
```

Le conflit indique : la table `posts`, la clé de ligne `42`, la colonne `title`, notre valeur `Welcome`, leur valeur `Greetings` et la valeur de base `Hello`.

### update_delete

Un côté a mis à jour une ligne et l'autre l'a supprimée. merql ne peut pas déterminer si la mise à jour ou la suppression doit l'emporter.

```
Ours:    posts #42  title = "Updated Title"    (updated)
Theirs:  posts #42  (deleted)
```

Le conflit indique : la table `posts`, la clé de ligne `42` et le type `update_delete`. Notre valeur contient la ligne entière après mise à jour. Leur valeur est `null`.

### delete_update

Le cas inverse de update_delete : ils ont mis à jour la ligne et nous l'avons supprimée.

```
Ours:    posts #42  (deleted)
Theirs:  posts #42  title = "Updated Title"    (updated)
```

Le conflit indique : type `delete_update`. Notre valeur est `null` ; leur valeur contient la ligne entière après mise à jour.

### insert_insert

Les deux côtés ont inséré une ligne avec la même clé primaire. Comme les deux lignes sont nouvelles (absentes de la base), merql ne peut pas les fusionner.

```
Base:    (no row #99)
Ours:    posts #99  { title: "My Post" }
Theirs:  posts #99  { title: "Their Post" }
```

Le conflit indique : type `insert_insert`. Notre valeur contient les données de notre ligne ; leur valeur contient les données de leur ligne.

## La classe Conflict

Chaque conflit est un objet en lecture seule avec les accesseurs suivants :

```php
$conflict->table();      // string: table name
$conflict->rowKey();     // string: encoded row identity key
$conflict->type();       // string: "update_update"|"update_delete"|"delete_update"|"insert_insert"
$conflict->column();     // ?string: column name (null for structural conflicts)
$conflict->oursValue();  // mixed: our value (null for delete side, full row for structural)
$conflict->theirsValue(); // mixed: their value (null for delete side, full row for structural)
$conflict->baseValue();  // mixed: base value (null for inserts)
```

La méthode `column()` renvoie un nom de colonne pour les conflits `update_update` et `null` pour les conflits structurels (`update_delete`, `delete_update`, `insert_insert`).

### Décoder la clé primaire

La clé de ligne est une chaîne encodée. Pour obtenir un tableau de clés primaires nommées, transmettez les colonnes d'identité :

```php
$pk = $conflict->primaryKey(['id']);
// ['id' => '42']

$pk = $conflict->primaryKey(['tenant_id', 'user_id']);
// ['tenant_id' => '1', 'user_id' => '99']
```

## Résolution automatique avec ConflictPolicy

`ConflictResolver` applique une politique pour résoudre tous les conflits en une seule fois :

```php
use Merql\Merge\ConflictPolicy;
use Merql\Merge\ConflictResolver;

// One side wins everywhere.
$resolved = ConflictResolver::resolve($result, ConflictPolicy::OursWins);
$resolved = ConflictResolver::resolve($result, ConflictPolicy::TheirsWins);

// Manual: return the result unchanged (inspect conflicts yourself).
$resolved = ConflictResolver::resolve($result, ConflictPolicy::Manual);
```

### Énumération ConflictPolicy

| Valeur | Comportement |
|---|---|
| `OursWins` | Utiliser notre valeur pour update_update. Conserver la mise à jour pour update_delete. Conserver la suppression pour delete_update. Utiliser notre insertion pour insert_insert. |
| `TheirsWins` | Utiliser leur valeur pour update_update. Supprimer pour update_delete. Conserver la mise à jour pour delete_update. Utiliser leur insertion pour insert_insert. |
| `Manual` | Renvoyer le résultat sans le modifier. Tous les conflits restent non résolus. |

### Fonctionnement de la résolution

Pour les conflits `update_update`, le résolveur modifie l'opération de fusion existante pour cette ligne, en remplaçant la valeur de la colonne en conflit par celle du côté gagnant.

Pour les conflits structurels (`update_delete`, `delete_update`, `insert_insert`), le résolveur ajoute une nouvelle opération au résultat :

- `update_delete` avec `OursWins` ajoute une opération UPDATE.
- `update_delete` avec `TheirsWins` ajoute une opération DELETE.
- `delete_update` avec `OursWins` ajoute une opération DELETE.
- `delete_update` avec `TheirsWins` ajoute une opération UPDATE.
- `insert_insert` utilise les valeurs de ligne du côté gagnant.

Le `MergeResult` résolu comporte une liste de conflits vide et peut être appliqué directement.

## Résolution manuelle

Pour un contrôle précis, examinez chaque conflit et construisez votre propre résolution :

```php
foreach ($result->conflicts() as $conflict) {
    echo "Table:  {$conflict->table()}\n";
    echo "Row:    {$conflict->rowKey()}\n";
    echo "Type:   {$conflict->type()}\n";

    if ($conflict->column() !== null) {
        echo "Column: {$conflict->column()}\n";
        echo "Base:   " . var_export($conflict->baseValue(), true) . "\n";
        echo "Ours:   " . var_export($conflict->oursValue(), true) . "\n";
        echo "Theirs: " . var_export($conflict->theirsValue(), true) . "\n";
    }
}
```

## Application avec des conflits non résolus

`Applier` refuse d'appliquer un résultat de fusion qui contient des conflits non résolus. Il lance une exception `ConflictException` :

```php
use Merql\Exceptions\ConflictException;

try {
    $applier->apply($result);
} catch (ConflictException $e) {
    echo $e->getMessage();
    // "Cannot apply merge with 3 unresolved conflicts"
}
```

Résolvez toujours les conflits avant d'appliquer le résultat, avec `ConflictResolver::resolve()` ou en construisant manuellement un `MergeResult` sans conflit.
