
# Сливане на ниво клетка

Сливането на ниво колона открива кога двете страни са променили една и съща колона. Сливането на ниво клетка отива по-надълбоко и обединява съдържанието в тази колона. Двама души могат да редактират различни редове в поле TEXT или различни ключове в обект JSON, без да възникне конфликт.

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

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

## Активиране на сливането на ниво клетка

Сливането на ниво клетка е по избор. То се изпълнява само в `ThreeWayMerge`, създаден с `CellMergeConfig`. Без такъв всички колони, които и двете страни са променили с различни стойности, пораждат конфликт `update_update`.

Фасадата `Merql` не приема конфигурация: `Merql::merge()`, `Merql::patch()` и `Merql::plan()` създават `new ThreeWayMerge()` без нея, а командата `merge` на CLI преминава през `Merql::merge()`. Те винаги сливат на ниво колона ([#7](https://github.com/inline0/merql/issues/7)).

За да слеете снимки на състоянието, записани от фасадата или CLI, със сливане на ниво клетка, заредете ги и извикайте `ThreeWayMerge` директно. Резултатът е обикновен `MergeResult`, който фасадата все още може да приложи, а `MergePlanBuilder` го преобразува в [план за сливане](https://merql.inline0.dev/bg/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);
```

Сливането на ниво клетка се изпълнява само за колони, променени и от двете страни. Ако колоната е променена само от едната страна, стойността ѝ се приема без промяна.

## CellMergeConfig

`CellMergeConfig` свързва колони или типове колони с реализации за сливане на ниво клетка. Фабриката `auto()` предоставя подходящи стойности по подразбиране:

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

Приоритет при избора на обединител:

1. Пълно име на колона (`table.column`).
2. Непълно име на колона (`column`).
3. Съвпадение по шаблон за тип на колона (без разлика между малки и главни букви `str_contains`).
4. По подразбиране: `OpaqueCellMerger` (винаги отчита конфликт при различни стойности).

## TextCellMerger

Слива многоредов текст чрез алгоритъма за разлики Myers, същия алгоритъм, който git използва за съдържанието на файлове. Всеки ред се третира като единица и промените в различни редове се сливат без конфликт.

```php
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();
```

### Пример: чисто сливане на текст

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

Редактирани са различни редове. Сливането създава `"LINE ONE\nline two\nLINE THREE"` без конфликт.

### Пример: конфликт в текст

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

И двете страни са променили един и същи ред с различни стойности. Обединителят връща резултат с конфликт. Обединеното съдържание включва маркери за конфликт, подобни на тези в git.

### Как работи

`TextCellMerger` използва `ThreeWayMerge::merge()` от pitmaster, който изчислява разликите Myers между базата и всяка страна, след което комбинира двата скрипта за редакции. Когато редакциите се застъпват в един и същ участък от редове, се отчита конфликт.

## JsonCellMerger

Слива обекти JSON, като сравнява всеки ключ от най-горното ниво поотделно. Всеки ключ се слива според стандартните правила за тристранно сливане.

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
```

### Пример: чисто сливане на 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}
```

Променени са различни ключове. Полученият резултат е:

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

### Пример: конфликт в JSON

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

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

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

И двете страни са променили ключа `color` с различни стойности. Това е конфликт. По подразбиране обединителят приема нашата стойност и отчита един конфликт.

### Правила на ниво ключ

| База | Нашето | Тяхното | Резултат |
|---|---|---|---|
| Ключът не е променен | Ключът не е променен | Приема се базата |
| Ключът не е променен | Ключът е променен | Приема се тяхната стойност |
| Ключът е променен | Ключът не е променен | Приема се нашата стойност |
| Ключът е променен (еднакво) | Ключът е променен (еднакво) | Приема се (съвпадат) |
| Ключът е променен (различно) | Ключът е променен (различно) | **Конфликт** (по подразбиране: нашата стойност) |
| Ключът съществува | Ключът съществува | Ключът е премахнат | Приема се премахването |
| Ключът съществува | Ключът е премахнат | Ключът съществува | Приема се премахването |
| Ключът липсва | Ключът е добавен | Ключът липсва | Приема се добавянето |
| Ключът липсва | Ключът липсва | Ключът е добавен | Приема се добавянето |

Вложените обекти се сравняват като непрозрачни низове JSON, а не рекурсивно. Ако вложен обект се различава, се сравнява цялата стойност на съответния ключ, а не подчинените му ключове.

### Изисквания

И трите стойности трябва да са валидни обекти или масиви JSON. Ако някоя стойност не е валидна JSON или е скаларна стойност JSON (низ, число, булева стойност), обединителят преминава към конфликт.

## OpaqueCellMerger

Обединителят по подразбиране, когато не е конфигурирана стратегия за сливане на ниво клетка. Той третира стойността на колоната като непрозрачен блок и винаги отчита конфликт, когато двете страни се различават.

```php
use Merql\CellMerge\OpaqueCellMerger;

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

## CellMergeResult

Всички обединители на ниво клетка връщат `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
```

Фабрични методи:

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

## Персонализирани обединители на ниво клетка

Реализирайте интерфейса `CellMerger`, за да добавите собствена стратегия за сливане:

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