
# Penggabungan Tingkat Sel

Penggabungan tingkat kolom mendeteksi jika kedua sisi mengubah kolom yang sama. Penggabungan tingkat sel bekerja lebih mendalam dengan menggabungkan isi di dalam kolom tersebut. Dua orang yang mengedit baris berbeda dalam kolom TEXT atau kunci berbeda pada objek JSON dapat menggabungkan perubahan dengan bersih.

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

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

## Mengaktifkan penggabungan tingkat sel

Penggabungan tingkat sel bersifat opsional. Penggabungan ini hanya berjalan dalam `ThreeWayMerge` yang dibuat dengan `CellMergeConfig`. Tanpanya, setiap kolom yang diubah kedua sisi menjadi nilai berbeda akan menjadi konflik `update_update`.

Fasade `Merql` tidak menerima konfigurasi: `Merql::merge()`, `Merql::patch()`, dan `Merql::plan()` membuat `new ThreeWayMerge()` tanpanya, dan perintah `merge` milik CLI dijalankan melalui `Merql::merge()`. Semuanya selalu menggabungkan pada tingkat kolom ([#7](https://github.com/inline0/merql/issues/7)).

Untuk menggabungkan snapshot yang disimpan oleh fasade atau CLI dengan penggabungan tingkat sel, muat snapshot tersebut dan panggil `ThreeWayMerge` sendiri. Hasilnya adalah `MergeResult` biasa, sehingga fasade tetap dapat menerapkannya, dan `MergePlanBuilder` mengubahnya menjadi [rencana penggabungan](https://merql.inline0.dev/id/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);
```

Penggabung sel hanya berjalan untuk kolom yang diubah kedua sisi. Jika hanya satu sisi yang mengubah kolom, nilainya digunakan apa adanya.

## CellMergeConfig

`CellMergeConfig` memetakan kolom atau jenis kolom ke implementasi penggabung sel. Factory `auto()` menyediakan nilai default yang sesuai:

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

Prioritas resolusi penggabung:

1. Nama kolom berkualifikasi (`table.column`).
2. Nama kolom tanpa kualifikasi (`column`).
3. Pencocokan pola jenis kolom (`str_contains` tanpa membedakan huruf besar dan kecil).
4. Default: `OpaqueCellMerger` (selalu melaporkan konflik jika nilainya berbeda).

## TextCellMerger

Menggabungkan teks multi-baris menggunakan diff Myers, algoritme yang sama dengan yang digunakan git untuk isi berkas. Setiap baris diperlakukan sebagai satu unit, dan perubahan pada baris yang berbeda dapat digabungkan dengan bersih.

```php
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();
```

### Contoh: penggabungan teks yang bersih

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

Baris yang berbeda diedit. Penggabungan menghasilkan `"LINE ONE\nline two\nLINE THREE"` tanpa konflik.

### Contoh: konflik teks

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

Kedua sisi mengubah baris yang sama menjadi nilai berbeda. Penggabung mengembalikan hasil konflik. Konten gabungan menyertakan penanda konflik yang mirip dengan git.

### Cara kerjanya

`TextCellMerger` mendelegasikan ke `ThreeWayMerge::merge()` milik pitmaster, yang menghitung diff Myers antara base dan tiap sisi, lalu menggabungkan kedua skrip edit. Jika edit saling tumpang tindih pada wilayah baris yang sama, konflik akan dilaporkan.

## JsonCellMerger

Menggabungkan objek JSON dengan membandingkan kunci tingkat atas secara terpisah. Setiap kunci digabungkan menggunakan aturan tiga arah standar.

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
```

### Contoh: penggabungan JSON yang bersih

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

Kunci yang berbeda diubah. Hasil gabungannya adalah:

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

### Contoh: konflik JSON

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

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

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

Kedua sisi mengubah kunci `color` menjadi nilai yang berbeda. Ini adalah konflik. Secara default, penggabung memilih ours dan melaporkan satu konflik.

### Aturan tingkat kunci

| Base | Ours | Theirs | Hasil |
|---|---|---|---|
| Kunci tidak berubah | Kunci tidak berubah | Terima base |
| Kunci tidak berubah | Kunci berubah | Terima theirs |
| Kunci berubah | Kunci tidak berubah | Terima ours |
| Kunci berubah (sama) | Kunci berubah (sama) | Terima (sepakat) |
| Kunci berubah (berbeda) | Kunci berubah (berbeda) | **Konflik** (default: ours) |
| Kunci ada | Kunci ada | Kunci dihapus | Terima penghapusan |
| Kunci ada | Kunci dihapus | Kunci ada | Terima penghapusan |
| Kunci tidak ada | Kunci ditambahkan | Kunci tidak ada | Terima penambahan |
| Kunci tidak ada | Kunci tidak ada | Kunci ditambahkan | Terima penambahan |

Objek bersarang dibandingkan sebagai string JSON tanpa struktur rekursif. Jika suatu objek bersarang berbeda, seluruh nilai kunci tersebut akan dibandingkan, bukan subkuncinya.

### Persyaratan

Ketiga nilai harus berupa objek atau larik JSON yang valid. Jika ada nilai yang bukan JSON valid, atau berupa skalar JSON (string, angka, boolean), penggabung akan kembali menggunakan konflik.

## OpaqueCellMerger

Penggabung default ketika tidak ada strategi tingkat sel yang dikonfigurasi. Penggabung ini memperlakukan nilai kolom sebagai blob buram dan selalu melaporkan konflik ketika kedua sisi berbeda.

```php
use Merql\CellMerge\OpaqueCellMerger;

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

## CellMergeResult

Semua penggabung sel mengembalikan `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
```

Metode factory:

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

## Penggabung sel kustom

Implementasikan antarmuka `CellMerger` untuk menambahkan strategi penggabungan Anda sendiri:

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