# @jucio.io/state/history

History management plugin for @jucio.io/state that provides undo/redo functionality with markers, grouping, and commit listeners.

## Features

- ⏪ **Undo/Redo**: Full undo and redo support with automatic change tracking
- 🏷️ **Markers**: Add descriptive markers to create logical undo/redo boundaries
- 📦 **Grouping**: Group multiple changes into a single undo/redo step (can span multiple calls; commit when done)
- 🔔 **Commit Listeners**: React to history commits
- 🎯 **Smart Change Consolidation**: Automatically merges changes to the same path
- 📏 **Configurable History Size**: Limit the number of stored changes

## Installation

```bash
npm install @jucio.io/state
```

**Note:** History plugin is included in the main package.

## Quick Start

```javascript
import { createState } from '@jucio.io/state';
import { HistoryManager } from '@jucio.io/state/history';

// Create state and install history plugin
const state = createState({
  data: { count: 0 }
});
state.install(HistoryManager);

// Make some changes
state.set(['data', 'count'], 1);
state.set(['data', 'count'], 2);
state.set(['data', 'count'], 3);

console.log(state.get(['data', 'count'])); // 3

// Undo the changes
state.history.undo();
console.log(state.get(['data', 'count'])); // 2

state.history.undo();
console.log(state.get(['data', 'count'])); // 1

// Redo
state.history.redo();
console.log(state.get(['data', 'count'])); // 2

// Check if undo/redo is available
console.log(state.history.canUndo()); // true
console.log(state.history.canRedo()); // true
```

## API Reference

### Actions

#### `undo(callback?)`

Undo the last committed change(s).

```javascript
state.history.undo(() => {
  console.log('Undo completed');
});
```

**Returns:** `boolean` - `true` if undo was successful, `false` if no changes to undo

#### `redo(callback?)`

Redo the next change(s).

```javascript
state.history.redo(() => {
  console.log('Redo completed');
});
```

**Returns:** `boolean` - `true` if redo was successful, `false` if no changes to redo

#### `canUndo()`

Check if undo is available.

```javascript
if (state.history.canUndo()) {
  state.history.undo();
}
```

**Returns:** `boolean`

#### `canRedo()`

Check if redo is available.

```javascript
if (state.history.canRedo()) {
  state.history.redo();
}
```

**Returns:** `boolean`

### Grouping

#### `group(callback?)`

Start grouping changes into a single undo/redo step. Grouping can span multiple calls; call `commit()` (or the returned function) when done. Pass a function to run in grouping mode and commit when it returns (quick single-block use).

```javascript
// Span multiple calls
state.history.group();
state.set(['user', 'name'], 'Alice');
state.set(['user', 'age'], 30);
state.history.commit(); // All changes are now a single undo/redo step

// Or use the returned function
const endGroup = state.history.group();
state.set(['user', 'email'], 'alice@example.com');
endGroup();

// Quick single-block: pass a callback
state.history.group(() => {
  state.set(['user', 'name'], 'Alice');
  state.set(['user', 'age'], 30);
});
state.history.undo(); // Undoes both changes at once
```

**Returns:** `HistoryManager` (for chaining) if a callback was provided; otherwise `Function` to call when done

#### `commit(description?)`

Manually commit pending changes and end the current group.

```javascript
state.history.group();
state.set(['data', 'value'], 1);
state.history.commit('Initial value'); // Commits and ends grouping
```

**Returns:** `HistoryManager` instance (for chaining)

### Markers

#### `addMarker(description)`

Add a descriptive marker to create a logical undo/redo boundary.

```javascript
state.set(['user', 'name'], 'Alice');
state.history.addMarker('Set user name');

state.set(['user', 'age'], 30);
state.history.addMarker('Set user age');

// Now each undo will stop at the marker
state.history.undo(); // Undoes age change
state.history.undo(); // Undoes name change
```

**Parameters:**
- `description` (string): Optional description for the marker

### Commit Listeners

#### `onCommit(callback)`

Listen for history commits.

```javascript
const unsubscribe = state.history.onCommit((changes) => {
  console.log('Changes committed:', changes);
});

state.set(['data', 'value'], 1);
// Console: "Changes committed: [...]"

// Remove listener when done
unsubscribe();
```

**Parameters:**
- `callback` (Function): Called with an array of changes when committed

**Returns:** `Function` - Call to remove the listener

### Info

#### `size()`

Get the current number of items in the history.

```javascript
const historySize = state.history.size();
console.log(`History contains ${historySize} items`);
```

**Returns:** `number`

## Configuration

Configure the history plugin using the `configure()` method:

```javascript
import { createState } from '@jucio.io/state';
import { HistoryManager } from '@jucio.io/state/history';

const state = createState({
  data: { count: 0 }
});

// Install with custom configuration
state.install(HistoryManager.configure({
  maxSize: 200  // Limit to 200 history items (default: 100)
}));
```

### Options

- **`maxSize`** (number): Maximum number of history items to keep. Default: `100`

## Advanced Usage

### Grouping with Markers

```javascript
// Start a complex operation (grouping can span multiple calls)
state.history.group();
state.history.addMarker('Start user registration');

state.set(['user', 'name'], 'Alice');
state.set(['user', 'email'], 'alice@example.com');
state.set(['user', 'preferences'], { theme: 'dark' });

state.history.commit();

// All changes are now a single undo step with a descriptive marker
```

### Pause and Resume Recording

```javascript
// Temporarily pause history recording (internal API)
state.plugins.history.pause();

state.set(['temp', 'data'], 'not recorded');

state.plugins.history.resume();

state.set(['tracked', 'data'], 'recorded'); // This will be recorded
```

### Reset History

```javascript
// Clear all history (internal API)
state.plugins.history.reset();
```

## How It Works

1. **Change Tracking**: The plugin automatically tracks all state changes
2. **Consolidation**: Multiple changes to the same path are consolidated
3. **Deferred Commits**: Changes are committed asynchronously for performance
4. **Markers**: Markers create logical boundaries for undo/redo operations
5. **Inversion**: Changes are inverted for undo operations

## Common Patterns

### Form Editing with Undo/Redo

```javascript
// Track form edits
function handleFieldChange(field, value) {
  state.history.group();
  state.set(['form', field], value);
  state.history.addMarker(`Update ${field}`);
  state.history.commit();
}

// Implement undo/redo buttons
function handleUndo() {
  if (state.history.canUndo()) {
    state.history.undo(() => {
      updateUI();
    });
  }
}
```

### Multi-Step Operations

```javascript
function performComplexOperation() {
  state.history.group();
  
  // Step 1: Update user
  state.set(['user', 'status'], 'processing');
  
  // Step 2: Create records
  state.set(['records'], [{ id: 1, status: 'new' }]);
  
  // Step 3: Update timestamp
  state.set(['lastUpdate'], Date.now());
  
  state.history.commit();
  state.history.addMarker('Complex operation completed');
}

// All steps undo/redo as one operation
```

## License

See the root [LICENSE](../../LICENSE) file for license information.

## Related Packages

- [@jucio.io/state](../../core) - Core state management system
- [@jucio.io/state/matcher](../matcher) - Path pattern matching
- [@jucio.io/state/on-change](../on-change) - Change listeners

