
# Scalanie na poziomie komórek

Scalanie na poziomie kolumn wykrywa, gdy obie strony zmieniły tę samą kolumnę. Scalanie na poziomie komórek sięga głębiej i scala zawartość tej kolumny. Dwie osoby edytujące różne wiersze pola TEXT lub różne klucze obiektu JSON mogą bezproblemowo scalić zmiany.

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

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

## Włączanie scalania na poziomie komórek

Scalanie na poziomie komórek jest opcjonalne. Działa tylko w obiekcie `ThreeWayMerge` utworzonym z `CellMergeConfig`. Bez niego każda kolumna, którą obie strony zmieniły na różne wartości, powoduje konflikt `update_update`.

Fasada `Merql` nie przyjmuje konfiguracji: `Merql::merge()`, `Merql::patch()` i `Merql::plan()` tworzą `new ThreeWayMerge()` bez niej, a polecenie `merge` obiektu CLI przechodzi przez `Merql::merge()`. Zawsze scalają na poziomie kolumn ([#7](https://github.com/inline0/merql/issues/7)).

Aby scalić zrzuty zapisane przez fasadę lub CLI z użyciem scalania na poziomie komórek, wczytaj je i samodzielnie wywołaj `ThreeWayMerge`. Wynik jest zwykłym obiektem `MergeResult`, więc fasada nadal może go zastosować, a `MergePlanBuilder` zmienia go w [plan scalania](https://merql.inline0.dev/pl/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);
```

Scalanie komórek działa tylko dla kolumn zmienionych przez obie strony. Gdy kolumnę zmieniła tylko jedna strona, jej wartość jest przyjmowana bez zmian.

## CellMergeConfig

`CellMergeConfig` mapuje kolumny lub typy kolumn na implementacje scalania komórek. Fabryka `auto()` udostępnia rozsądne wartości domyślne:

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

Priorytety wyboru mechanizmu scalania:

1. Kwalifikowana nazwa kolumny (`table.column`).
2. Niekwalifikowana nazwa kolumny (`column`).
3. Dopasowanie wzorca typu kolumny (bez rozróżniania wielkości liter `str_contains`).
4. Wartość domyślna: `OpaqueCellMerger` (zawsze zgłasza konflikt, gdy wartości się różnią).

## TextCellMerger

Łączy wielowierszowy tekst za pomocą algorytmu różnicowego Myersa, tego samego, którego git używa do zawartości plików. Każdy wiersz jest traktowany jako osobna jednostka, a zmiany w różnych wierszach łączą się bez konfliktów.

```php
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();
```

### Przykład: bezkonfliktowe scalanie tekstu

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

Edytowano różne wiersze. W wyniku scalania powstaje `"LINE ONE\nline two\nLINE THREE"` bez konfliktu.

### Przykład: konflikt tekstowy

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

Obie strony zmieniły ten sam wiersz na różne wartości. Narzędzie scalające zwraca wynik z konfliktem. Scalone treści zawierają znaczniki konfliktu podobne do git.

### Jak to działa

`TextCellMerger` korzysta z `ThreeWayMerge::merge()` w pitmaster, który oblicza różnice Myersa między bazą a każdą ze stron, a następnie łączy oba zestawy zmian. Jeśli zmiany nakładają się na ten sam obszar wiersza, zgłasza konflikt.

## JsonCellMerger

Scala obiekty JSON, porównując niezależnie klucze najwyższego poziomu. Każdy klucz jest scalany zgodnie ze standardowymi regułami trójstronnymi.

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
```

### Przykład: bezkonfliktowe scalanie 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}
```

Zmieniono różne klucze. Wynik scalania:

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

### Przykład: konflikt JSON

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

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

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

Obie strony zmieniły klucz `color` na różne wartości. To konflikt. Domyślnie mechanizm scalania zachowuje naszą wartość i zgłasza jeden konflikt.

### Reguły na poziomie kluczy

| Baza | Nasza strona | Druga strona | Wynik |
|---|---|---|---|
| Klucz bez zmian | Klucz bez zmian | Przyjmij bazę |
| Klucz bez zmian | Klucz zmieniony | Przyjmij wartość drugiej strony |
| Klucz zmieniony | Klucz bez zmian | Przyjmij naszą wartość |
| Klucz zmieniony (tak samo) | Klucz zmieniony (tak samo) | Przyjmij (zgodne) |
| Klucz zmieniony (inaczej) | Klucz zmieniony (inaczej) | **Konflikt** (domyślnie: nasza wartość) |
| Klucz istnieje | Klucz istnieje | Klucz usunięty | Przyjmij usunięcie |
| Klucz istnieje | Klucz usunięty | Klucz istnieje | Przyjmij usunięcie |
| Brak klucza | Dodano klucz | Brak klucza | Przyjmij dodanie |
| Brak klucza | Brak klucza | Dodano klucz | Przyjmij dodanie |

Zagnieżdżone obiekty są porównywane jako nieprzezroczyste ciągi JSON, bez rekurencji. Jeśli zagnieżdżony obiekt się różni, porównywana jest cała wartość danego klucza, a nie jego podklucze.

### Wymagania

Wszystkie trzy wartości muszą być prawidłowymi obiektami lub tablicami JSON. Jeśli którakolwiek wartość nie jest prawidłowym JSON lub jest skalarem JSON (ciągiem, liczbą, wartością logiczną), mechanizm scalania zgłasza konflikt.

## OpaqueCellMerger

Domyślny mechanizm scalania, gdy nie skonfigurowano strategii na poziomie komórek. Traktuje wartość kolumny jako nieprzezroczysty blok i zawsze zgłasza konflikt, gdy obie strony mają różne wartości.

```php
use Merql\CellMerge\OpaqueCellMerger;

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

## CellMergeResult

Wszystkie mechanizmy scalania komórek zwracają wartość `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 fabrykujące:

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

## Własne mechanizmy scalania komórek

Zaimplementuj interfejs `CellMerger`, aby dodać własną strategię scalania:

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