
# Combinación a nivel de celda

La combinación a nivel de columna detecta cuándo ambas partes modificaron la misma columna. La combinación a nivel de celda profundiza y combina el contenido de esa columna. Dos personas que editan líneas distintas de un campo TEXT, o claves distintas de un objeto JSON, pueden combinar sus cambios sin conflictos.

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

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

## Cómo habilitar la combinación a nivel de celda

La combinación a nivel de celda es opcional. Solo se ejecuta en un `ThreeWayMerge` construido con un `CellMergeConfig`. Sin él, cada columna que ambas partes modificaron con valores distintos produce un conflicto `update_update`.

La fachada `Merql` no acepta una configuración: `Merql::merge()`, `Merql::patch()` y `Merql::plan()` construyen `new ThreeWayMerge()` sin ella, y el comando `merge` de CLI pasa por `Merql::merge()`. Siempre combinan a nivel de columna ([#7](https://github.com/inline0/merql/issues/7)).

Para combinar instantáneas guardadas por la fachada o CLI con combinación a nivel de celda, cárguelas y llame a `ThreeWayMerge` directamente. El resultado es un `MergeResult` normal, así que la fachada aún puede aplicarlo, y `MergePlanBuilder` lo convierte en un [plan de combinación](https://merql.inline0.dev/es/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);
```

Los combinadores de celdas solo se ejecutan en las columnas que ambas partes modificaron. Si solo una parte modificó una columna, se toma su valor tal cual.

## CellMergeConfig

`CellMergeConfig` asigna implementaciones de combinadores de celdas a columnas o tipos de columna. La fábrica `auto()` proporciona valores predeterminados adecuados:

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

Prioridad de resolución de combinadores:

1. Nombre de columna cualificado (`table.column`).
2. Nombre de columna sin cualificar (`column`).
3. Coincidencia de patrón de tipo de columna (`str_contains` sin distinguir mayúsculas de minúsculas).
4. Predeterminado: `OpaqueCellMerger` (siempre informa de un conflicto cuando los valores difieren).

## TextCellMerger

Combina texto de varias líneas mediante el algoritmo Myers diff, el mismo que usa git para el contenido de archivos. Cada línea se trata como una unidad, y los cambios en líneas distintas se combinan sin conflictos.

```php
use Merql\CellMerge\TextCellMerger;

$merger = new TextCellMerger();
```

### Ejemplo: combinación de texto sin conflictos

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

Se editaron líneas distintas. La combinación produce `"LINE ONE\nline two\nLINE THREE"` sin conflictos.

### Ejemplo: conflicto de texto

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

Ambas partes cambiaron la misma línea por valores distintos. El combinador devuelve un resultado con conflictos. El contenido combinado incluye marcadores de conflicto similares a los de git.

### Cómo funciona

`TextCellMerger` delega en `ThreeWayMerge::merge()` de pitmaster, que calcula una diferencia Myers entre la base y cada parte y luego combina los dos conjuntos de ediciones. Si las ediciones se solapan en la misma región de líneas, informa de un conflicto.

## JsonCellMerger

Combina objetos JSON comparando de forma independiente las claves de nivel superior. Cada clave se combina con las reglas estándar de tres vías.

```php
use Merql\CellMerge\JsonCellMerger;

$merger = new JsonCellMerger();
```

### Ejemplo: combinación JSON sin conflictos

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

Se cambiaron claves distintas. El resultado combinado es:

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

### Ejemplo: conflicto JSON

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

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

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

Ambas partes cambiaron la clave `color` por valores distintos. Esto es un conflicto. De forma predeterminada, el combinador conserva el valor de nuestro lado e informa de un conflicto.

### Reglas a nivel de clave

| Base | Nuestro lado | Su lado | Resultado |
|---|---|---|---|
| Clave sin cambios | Clave sin cambios | Aceptar la base |
| Clave sin cambios | Clave modificada | Aceptar el valor de su lado |
| Clave modificada | Clave sin cambios | Aceptar el valor de nuestro lado |
| Clave modificada (igual) | Clave modificada (igual) | Aceptar (coinciden) |
| Clave modificada (distinta) | Clave modificada (distinta) | **Conflicto** (predeterminado: nuestro lado) |
| La clave existe | La clave existe | La clave se eliminó | Aceptar la eliminación |
| La clave existe | La clave se eliminó | La clave existe | Aceptar la eliminación |
| Falta la clave | Se añadió la clave | Falta la clave | Aceptar la adición |
| Falta la clave | Falta la clave | Se añadió la clave | Aceptar la adición |

Los objetos anidados se comparan como cadenas JSON opacas, sin comparación recursiva. Si un objeto anidado difiere, se compara el valor completo de esa clave, no sus subclaves.

### Requisitos

Los tres valores deben ser objetos o matrices JSON válidos. Si algún valor no es JSON válido o es un escalar JSON (cadena, número o booleano), el combinador recurre a un conflicto.

## OpaqueCellMerger

El combinador predeterminado cuando no se configura una estrategia a nivel de celda. Trata el valor de la columna como un bloque opaco e informa siempre de un conflicto cuando los valores de ambas partes difieren.

```php
use Merql\CellMerge\OpaqueCellMerger;

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

## CellMergeResult

Todos los combinadores de celdas devuelven un `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
```

Métodos de fábrica:

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

## Combinadores de celdas personalizados

Implemente la interfaz `CellMerger` para añadir su propia estrategia de combinación:

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