
# الدمج على مستوى الخلية

يكشف الدمج على مستوى الأعمدة ما إذا كان الجانبان قد غيّرا العمود نفسه. أما الدمج على مستوى الخلية فيتعمق أكثر، إذ يدمج المحتوى داخل ذلك العمود. ويمكن لشخصين يعدّلان سطرين مختلفين في حقل 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/ar/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());
```
