import { Meta, Canvas } from '@storybook/addon-docs/blocks'
import * as OnBulkCellChangeStories from './on-bulk-cell-change.stories.tsx'

<Meta title="pv-grid/Components/SpreadsheetGrid (Experimental)/onBulkCellChange" />

# onBulkCellChange

The `onBulkCellChange` prop is a callback function that is invoked when multiple cells are modified simultaneously in the `SpreadsheetGrid`. This occurs during bulk edit operations such as paste operations or (future) fill handle usage. Unlike `onCellChange` which fires for individual cell edits, `onBulkCellChange` receives all changes at once, allowing for efficient batch processing and validation.

## Type definition

`onBulkCellChange` accepts a callback function of type:

```tsx
type onBulkCellChange<TDataModel extends GridRowData> = (
    payload: Map<TDataModel['id'], GridConfirmPayload<TDataModel>[]>
) => boolean | void
```

- **payload**: A `Map` where each key is a row ID and each value is an array of `GridConfirmPayload` objects representing all the changes for that row.
- **return: boolean or undefined**: `false` to indicate the paste failed, or `true`/`void`/`undefined` to accept the changes (default behavior).

## How it works

When a user performs a bulk operation like pasting data, the grid:

1. Collects all the individual cell changes that would occur
2. Groups them by row ID
3. Calls `onBulkCellChange` with the grouped changes
4. If the bulk changes are not rejected (by returning `false`):
    - It will indicate to the user data was successfully updated.
    - When pasting: it may expand the current range selection based on the size of the pasted data.
    - When pasting: it may change the focus to the top-left cell of the pasted range (varies in some cases).

### Notes about pasting

When pasting data into the grid, the grid will first look for `application/x-pvds-grid` data on the clipboard and will use the `value` from the data if found (This is automatically written by the `onRangeCopy` presets). In this case, the `nextValue` in the payload will be the original data type (string, number, boolean) when copied from another PVDS grid. If pasting from an external application (like Excel or Google Sheets), the `nextValue` will always be a string and the grid will first try to use the `text/html` content from the clipboard, falling back to `text/plain` if no HTML content is found.

## Basic usage

In this example, we handle bulk cell changes by updating our data state and providing basic validation for numeric values.

Try selecting a range of cells and pasting data (Ctrl+V / Cmd+V) to see bulk cell changes in action.

<Canvas of={OnBulkCellChangeStories.BasicOnBulkCellChange} />

## Validation with rejection

This example demonstrates validation that rejects negative numbers. Try pasting negative values to see the validation in action.

<Canvas of={OnBulkCellChangeStories.ValidationWithRejection} />

In most cases, if a value is not valid you will want to reject the full paste, and report an error.

```tsx
const onBulkCellChange = (payloads) => {
    // Validate all changes before applying any
    for (const [rowId, rowPayloads] of payloads) {
        for (const payload of rowPayloads) {
            if (!isValidValue(payload.nextValue)) {
                showMessage({
                    type: 'error',
                    message: `Values on the clipboard were not valid for these cells and could not be pasted.`,
                })
                return false // Reject all changes
            }
        }
    }

    // All validations passed, apply changes
    applyBulkChanges(payloads)
    return true
}
```

## Common patterns

### Processing the payload Map

The payload is a `Map` where keys are row IDs and values are arrays of change payloads for that row:

```tsx
const onBulkCellChange = (payloads) => {
    // Iterate over each row that has changes
    for (const [rowId, rowPayloads] of payloads) {
        console.log(`Row ${rowId} has ${rowPayloads.length} changes`)

        // Process each cell change in this row
        rowPayloads.forEach((payload) => {
            console.log(
                `Column ${payload.columnId}: ${payload.previousValue} → ${payload.nextValue}`
            )
        })
    }
}
```

### Batch data updates

```tsx
const onBulkCellChange = (payloads) => {
    setData((currentData) =>
        currentData.map((row) => {
            if (payloads.has(row.id)) {
                const rowChanges = payloads.get(row.id)!
                const updates = {}

                rowChanges.forEach((change) => {
                    /* Note: nextValue may be the original data type (string, number, boolean) when copied
                       value is from another PVDS grid. It will always be a string when pasting from an external application. */
                    updates[change.columnId] = processValue(change.nextValue)
                })

                return { ...row, ...updates }
            }
            return row
        })
    )
}
```

## Best practices

1. **Always validate the entire batch**: If any single change is invalid, consider rejecting the entire operation to maintain data consistency.
2. **Use efficient data updates**: Since you're updating multiple cells at once, this is a good opportunity to optimize your data updates.
3. **Provide user feedback**: When rejecting changes, always provide clear feedback about why the operation failed.
4. **Handle type conversion**: Bulk operations often involve pasted text data that needs to be converted to appropriate types.
5. **Consider performance**: For large bulk operations, be mindful of the performance impact of your validation and update logic.

## Relationship to onCellChange

- `onCellChange`: Fires for individual cell edits (edit, type, confirm)
- `onBulkCellChange`: Fires for bulk operations (paste, in the future fill handle) that affect multiple cells

Both callbacks serve different purposes and can be used together in the same grid. If you have both callbacks defined and a bulk operation occurs, only `onBulkCellChange` will fire - `onCellChange` will not fire for each individual cell in the bulk operation.
