# @jucio.io/state/matcher

Path-based state watching plugin for @jucio.io/state that allows you to watch specific paths in your state tree and react to changes. **Your handlers receive the current data at the watched path, not change objects.**

## Features

- 🎯 **Path Matching**: Watch specific paths in your state tree
- 📊 **Data Values**: Handlers receive actual data at the path, not change objects
- 🌳 **Hierarchical Watching**: Match exact paths, parent paths, or child paths
- 📦 **Automatic Batching**: Changes are automatically batched and debounced
- 🔄 **Smart Consolidation**: Multiple changes to the same path are consolidated
- 🎬 **Declarative Setup**: Define matchers during state initialization
- 🔌 **Plugin Architecture**: Seamlessly integrates with @jucie.io/state

## Installation

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

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

## Quick Start

```javascript
import { createState } from '@jucio.io/state';
import { Matcher, createMatcher } from '@jucio.io/state/matcher';

// Create a matcher
const userMatcher = createMatcher(['user'], (userData) => {
  console.log('User data:', userData);
});

// Create state and install matcher plugin
const state = createState({
  user: { name: 'Alice', age: 30 },
  settings: { theme: 'dark' }
});

// Install with initial matchers
state.install(Matcher.configure({
  matchers: [userMatcher]
}));

// Change user data - matcher receives the NEW data
state.set(['user', 'name'], 'Bob');
// Console: "User data: { name: 'Bob', age: 30 }"

// Change settings - matcher doesn't fire (different path)
state.set(['settings', 'theme'], 'light');
```

## API Reference

### Creating Matchers

#### `createMatcher(path, handler)`

Create a matcher that watches a specific path in the state tree.

**The handler receives the current data at the watched path, not change objects.**

```javascript
import { createMatcher } from '@jucio.io/state/matcher';

const matcher = createMatcher(['users', 'profile'], (profileData) => {
  console.log('Profile is now:', profileData);
});
```

**Parameters:**
- `path` (Array): Path to watch (e.g., `['user']`, `['users', 'profile']`)
- `handler` (Function): Callback function that receives the **current data** at the path

**Returns:** Matcher function that can be added to the plugin

### Plugin Actions

When using the Matcher plugin with a state instance, you get access to these actions:

#### `state.matcher.createMatcher(path, handler)`

Create and automatically register a matcher.

```javascript
import { createState } from '@jucio.io/state';
import { Matcher } from '@jucio.io/state/matcher';

const state = createState({ user: { name: 'Alice' } });
state.install(Matcher);

const unsubscribe = state.matcher.createMatcher(['user'], (userData) => {
  console.log('User is now:', userData);
});

// Later: remove the matcher
unsubscribe();
```

**Returns:** Unsubscribe function

#### `state.matcher.addMatcher(matcher)`

Add an existing matcher to the plugin.

```javascript
const matcher = createMatcher(['user'], (userData) => {
  console.log('User:', userData);
});

state.matcher.addMatcher(matcher);
```

#### `state.matcher.removeMatcher(matcher)`

Remove a matcher from the plugin.

```javascript
state.matcher.removeMatcher(matcher);
```

## Match Types

Matchers use hierarchical matching with three types. **The handler always receives the current data at the matched path:**

### Exact Match

Watches the exact path specified:

```javascript
const matcher = createMatcher(['user', 'profile'], (profileData) => {
  console.log('Profile data:', profileData);
});

state.set(['user', 'profile'], { bio: 'Hello' }); // ✅ Fires with { bio: 'Hello' }
state.set(['user', 'profile', 'bio'], 'Hi');       // ✅ Fires with { bio: 'Hi' }
state.set(['user'], { profile: { bio: 'Hi' } });   // ✅ Fires with { bio: 'Hi' }
state.set(['user', 'settings'], {});                // ❌ Doesn't fire
```

### Parent Match

Fires when a parent path or any descendant changes:

```javascript
const matcher = createMatcher(['user'], (userData) => {
  console.log('User data:', userData);
});

state.set(['user', 'name'], 'Alice');               // ✅ Fires with entire user object
state.set(['user', 'profile', 'bio'], 'Hello');     // ✅ Fires with entire user object
state.set(['user'], { name: 'Bob' });               // ✅ Fires with { name: 'Bob' }
```

### Child Match

When watching a parent and children change, child changes are consolidated:

```javascript
const matcher = createMatcher(['users'], (usersData) => {
  console.log('Users data:', usersData);
});

state.set(['users', 'alice'], { name: 'Alice' });
state.set(['users', 'bob'], { name: 'Bob' });

// Both changes are batched. Handler receives the full current state:
// { alice: { name: 'Alice' }, bob: { name: 'Bob' } }
```

## Configuration

### Initialize with Matchers

```javascript
import { createState } from '@jucio.io/state';
import { Matcher, createMatcher } from '@jucio.io/state/matcher';

const userMatcher = createMatcher(['user'], (user) => {
  console.log('User:', user);
});

const settingsMatcher = createMatcher(['settings'], (settings) => {
  console.log('Settings:', settings);
});

const state = createState({ user: {}, settings: {} });
state.install(Matcher.configure({
  matchers: [userMatcher, settingsMatcher]
}));
```

### Define Matchers as Objects

```javascript
import { createState } from '@jucio.io/state';
import { Matcher } from '@jucio.io/state/matcher';

const state = createState({ user: {}, settings: {} });
state.install(Matcher.configure({
  matchers: [
    {
      path: ['user'],
      handler: (user) => console.log('User:', user)
    },
    {
      path: ['settings'],
      handler: (settings) => console.log('Settings:', settings)
    }
  ]
}));
```

## Advanced Usage

### Multiple Matchers on Same Path

```javascript
const logger = createMatcher(['user'], (userData) => {
  console.log('User changed:', userData);
});

const validator = createMatcher(['user'], (userData) => {
  if (!userData.email) {
    console.warn('User has no email!');
  }
});

state.matcher.addMatcher(logger);
state.matcher.addMatcher(validator);

state.set(['user'], { name: 'Alice' });
// Both matchers fire with { name: 'Alice' }
```

### Dynamic Matcher Management

```javascript
// Add matcher conditionally
if (process.env.NODE_ENV === 'development') {
  const debugMatcher = state.matcher.createMatcher(['*'], (data) => {
    console.log('DEBUG: State changed:', data);
  });
}

// Add/remove based on user settings
function toggleAuditLog(enabled) {
  if (enabled) {
    const auditMatcher = createMatcher(['data'], (data) => {
      logToServer('data-change', data);
    });
    state.matcher.addMatcher(auditMatcher);
    return () => state.matcher.removeMatcher(auditMatcher);
  }
}
```

### Nested Path Watching

```javascript
// Watch different levels of nesting
const userMatcher = createMatcher(['user'], (userData) => {
  console.log('Entire user object:', userData);
});

const profileMatcher = createMatcher(['user', 'profile'], (profileData) => {
  console.log('Just profile:', profileData);
});

const nameMatcher = createMatcher(['user', 'profile', 'name'], (name) => {
  console.log('Just name:', name);
});

state.set(['user', 'profile', 'name'], 'Alice');
// All three matchers fire:
// - userMatcher gets the entire user object
// - profileMatcher gets just the profile object  
// - nameMatcher gets just 'Alice'
```

### Batching and Consolidation

```javascript
const matcher = createMatcher(['items'], (itemsData) => {
  console.log('Items:', itemsData);
});

// Multiple rapid changes are batched
state.set(['items', 'item1'], { value: 1 });
state.set(['items', 'item2'], { value: 2 });
state.set(['items', 'item3'], { value: 3 });

// Single callback with current state:
// { item1: { value: 1 }, item2: { value: 2 }, item3: { value: 3 } }
```

## Common Patterns

### Form Field Validation

```javascript
const emailMatcher = createMatcher(['form', 'email'], (email) => {
  const isValid = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  state.set(['form', 'errors', 'email'], isValid ? null : 'Invalid email');
});

state.matcher.addMatcher(emailMatcher);
```

### Persistence

```javascript
const persistMatcher = createMatcher(['user', 'preferences'], (preferences) => {
  localStorage.setItem('preferences', JSON.stringify(preferences));
});

state.matcher.addMatcher(persistMatcher);
```

### Analytics Tracking

```javascript
const analyticsMatcher = createMatcher(['analytics', 'events'], (events) => {
  Object.entries(events).forEach(([key, event]) => {
    trackEvent(event.name, event.properties);
  });
});

state.matcher.addMatcher(analyticsMatcher);
```

### Derived State Updates

```javascript
// Update derived state when source changes
const cartMatcher = createMatcher(['cart', 'items'], (items) => {
  const total = items.reduce((sum, item) => sum + item.price * item.quantity, 0);
  state.set(['cart', 'total'], total);
});

state.matcher.addMatcher(cartMatcher);
```

### API Synchronization

```javascript
const syncMatcher = createMatcher(['user'], async (userData) => {
  try {
    await fetch('/api/user', {
      method: 'PUT',
      body: JSON.stringify(userData)
    });
    console.log('User synced to server');
  } catch (error) {
    console.error('Failed to sync user:', error);
  }
});

state.matcher.addMatcher(syncMatcher);
```

## Performance Considerations

1. **Automatic Batching**: Matchers automatically batch changes using `setTimeout(fn, 0)`, so multiple synchronous changes trigger the handler only once

2. **Smart Consolidation**: Multiple changes to the same path are consolidated into a single update

3. **Efficient Matching**: Uses marker comparison for fast path matching

4. **Cleanup**: Always unsubscribe matchers when they're no longer needed to prevent memory leaks

```javascript
// Good: Clean up when done
const unsubscribe = state.matcher.createMatcher(['temp'], handler);
// ... later
unsubscribe();
```

## Comparison with OnChange Plugin

| Feature | Matcher | OnChange |
|---------|---------|----------|
| What handler receives | Current data at path | Change objects with metadata |
| Scope | Specific paths | Global changes |
| Batching | Automatic | Automatic |
| Consolidation | Smart path-based | By change address |
| Performance | Optimized for specific paths | Tracks all changes |
| Use Case | Watch specific data, get values | Track all changes, get metadata |

**Use Matcher when:** You want the current data at specific paths
**Use OnChange when:** You need change metadata (from/to values, method, etc.)

## License

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

## Related Packages
- [@jucio.io/react](https://github.com/adrianjonmiller/react) - React integration
- [@jucio.io/state](../../core) - Core state management system
- [@jucio.io/state/history](../history) - Undo/redo functionality
- [@jucio.io/state/on-change](../on-change) - Global change listeners

