
# Sloučení na úrovni buněk

Sloučení na úrovni sloupců rozpozná, když obě strany změnily stejný sloupec. Sloučení na úrovni buněk jde ještě dál a slučuje obsah uvnitř daného sloupce. Dva lidé mohou bez problémů sloučit změny, když upravují různé řádky pole TEXT nebo různé klíče objektu JSON.

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

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

## Zapnutí slučování na úrovni buněk

Sloučení na úrovni buněk je volitelné. Probíhá pouze v instanci `ThreeWayMerge` vytvořené s `CellMergeConfig`. Bez něj každá změna téhož sloupce na různé hodnoty na obou stranách představuje konflikt `update_update`.

Rozhraní `Merql` nepřijímá konfiguraci: `Merql::merge()`, `Merql::patch()` a `Merql::plan()` vytvářejí `new ThreeWayMerge()` bez ní a příkaz `merge` v CLI prochází přes `Merql::merge()`. Vždy slučují pouze na úrovni sloupců ([#7](https://github.com/inline0/merql/issues/7)).

Chcete-li snímky uložené pomocí rozhraní nebo CLI sloučit na úrovni buněk, načtěte je a sami zavolejte `ThreeWayMerge`. Výsledkem je běžné `MergeResult`, které může rozhraní stále použít, a `MergePlanBuilder` z něj vytvoří [plán sloučení](https://merql.inline0.dev/cs/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);
```

Slučování buněk probíhá pouze u sloupců změněných na obou stranách. Pokud sloupec změnila jen jedna strana, jeho hodnota se převezme beze změny.

## CellMergeConfig

`CellMergeConfig` mapuje sloupce nebo typy sloupců na implementace slučovačů buněk. Továrna `auto()` poskytuje vhodné výchozí hodnoty:

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

Pořadí priorit při výběru slučovače:

1. Plně kvalifikovaný název sloupce (`table.column`).
2. Nekvalifikovaný název sloupce (`column`).
3. Shoda se vzorem typu sloupce (bez rozlišení velikosti písmen `str_contains`).
4. Výchozí: `OpaqueCellMerger` (při rozdílných hodnotách vždy hlásí konflikt).

## TextCellMerger

Sloučí víceřádkový text pomocí Myersova diffu, stejného algoritmu, jaký git používá pro obsah souborů. Každý řádek se považuje za jednotku a změny v různých řádcích se sloučí bez problémů.

```php
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();
```

### Příklad: čisté sloučení textu

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

Byly upraveny různé řádky. Sloučení vytvoří `"LINE ONE\nline two\nLINE THREE"` bez konfliktu.

### Příklad: konflikt v textu

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

Obě strany změnily stejný řádek na různé hodnoty. Slučovací nástroj vrátí výsledek s konfliktem. Sloučený obsah zahrnuje značky konfliktu podobné těm v git.

### Jak to funguje

`TextCellMerger` deleguje na `ThreeWayMerge::merge()` z pitmaster, který vypočítá Myersův diff mezi základem a každou stranou a poté zkombinuje oba skripty úprav. Pokud se úpravy překrývají ve stejné oblasti řádků, nahlásí konflikt.

## JsonCellMerger

Sloučí objekty JSON porovnáním klíčů nejvyšší úrovně zvlášť. Každý klíč se sloučí pomocí standardních pravidel třícestného sloučení.

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
```

### Příklad: čisté sloučení JSON

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

Byly změněny různé klíče. Výsledek sloučení je:

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

### Příklad: konflikt JSON

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

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

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

Obě strany změnily klíč `color` na různé hodnoty. Jde o konflikt. Slučovací nástroj ve výchozím nastavení upřednostní naši verzi a nahlásí jeden konflikt.

### Pravidla na úrovni klíčů

| Základ | Naše | Jejich | Výsledek |
|---|---|---|---|
| Klíč beze změny | Klíč beze změny | Přijmout základ |
| Klíč beze změny | Klíč změněn | Přijmout jejich |
| Klíč změněn | Klíč beze změny | Přijmout naši verzi |
| Klíč změněn (stejně) | Klíč změněn (stejně) | Přijmout (shoda) |
| Klíč změněn (jinak) | Klíč změněn (jinak) | **Konflikt** (výchozí: naše verze) |
| Klíč existuje | Klíč existuje | Klíč odstraněn | Přijmout odstranění |
| Klíč existuje | Klíč odstraněn | Klíč existuje | Přijmout odstranění |
| Klíč chybí | Klíč přidán | Klíč chybí | Přijmout přidání |
| Klíč chybí | Klíč chybí | Klíč přidán | Přijmout přidání |

Vnořené objekty se porovnávají jako neprůhledné řetězce JSON, nikoli rekurzivně. Pokud se vnořený objekt liší, porovnává se celá hodnota daného klíče, nikoli jeho podklíče.

### Požadavky

Všechny tři hodnoty musí být platné objekty nebo pole JSON. Pokud některá hodnota není platné JSON nebo jde o skalární hodnotu JSON (řetězec, číslo, logická hodnota), slučovací nástroj přejde na konflikt.

## OpaqueCellMerger

Výchozí slučovací nástroj, pokud není nakonfigurována strategie na úrovni buněk. S hodnotou sloupce zachází jako s neprůhledným blokem a při rozdílu mezi oběma stranami vždy hlásí konflikt.

```php
use Merql\CellMerge\OpaqueCellMerger;

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

## CellMergeResult

Všechny slučovací nástroje buněk vracejí `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
```

Metody továrny:

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

## Vlastní slučovací nástroje buněk

Implementujte rozhraní `CellMerger` a přidejte vlastní strategii sloučení:

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