# DialogBuilder

The `DialogBuilder` provides a programmatic way to create and manage dialogs in the Quick API application. It integrates with the Activity lifecycle and supports unsaved changes detection.

## Basic Usage

```typescript
import { DialogBuilder } from '../builders/DialogBuilder.js'

// Create a simple dialog
const dialog = new DialogBuilder(this)
  .setTitle('Confirmation')
  .setDescription('Are you sure you want to proceed?')
  .setPositiveAction({
    label: 'Yes',
    action: () => {
      console.log('User confirmed')
    }
  })
  .setNegativeAction({
    label: 'No',
    action: () => {
      console.log('User cancelled')
    }
  })
  .open()
```

## Unsaved Changes Detection

The `DialogBuilder` now supports detecting unsaved changes and showing a warning dialog when users try to close a dialog with unsaved changes.

### Basic Setup

```typescript
const dialog = new DialogBuilder(this)
  .setTitle('Edit Item')
  .setDescription('Make your changes below')
  .setUnsavedChangesDetection({
    hasUnsavedChanges: () => this.hasUnsavedChanges(),
    warningTitle: 'Unsaved Changes',
    warningMessage: 'You have unsaved changes. Are you sure you want to discard them?',
    discardLabel: 'Discard Changes',
    keepEditingLabel: 'Keep Editing'
  })
  .open()
```

### Configuration Options

The `setUnsavedChangesDetection()` method accepts an `UnsavedChangesOptions` object:

```typescript
interface UnsavedChangesOptions {
  /**
   * Function that returns true if there are unsaved changes
   */
  hasUnsavedChanges: () => boolean
  
  /**
   * Title for the unsaved changes warning dialog
   */
  warningTitle?: string
  
  /**
   * Message for the unsaved changes warning dialog
   */
  warningMessage?: string
  
  /**
   * Label for the "Discard Changes" button
   */
  discardLabel?: string
  
  /**
   * Label for the "Keep Editing" button
   */
  keepEditingLabel?: string
}
```

### Implementation Example

Here's a complete example of how to implement unsaved changes detection in an activity:

```typescript
import { html, css, type TemplateResult, type CSSResult } from 'lit'
import { state } from 'lit/decorators.js'
import { ModalActivity } from '@api-client/ui/core/ModalActivity.js'
import { DialogBuilder } from '@api-client/ui/builders/DialogBuilder.js'
import { adaptStatic } from '@api-client/ui/decorators/styles.js'

export default class EditItemActivity extends ModalActivity {
  // Original values when dialog was opened
  private originalValues = {
    name: '',
    description: ''
  }

  // Current form values
  @state() accessor formValues = {
    name: '',
    description: ''
  }

  override async onCreate(): Promise<void> {
    // Load initial data
    this.originalValues = {
      name: 'Example Item',
      description: 'Initial description'
    }
    this.formValues = { ...this.originalValues }
  }

  /**
   * Check if there are unsaved changes
   */
  private hasUnsavedChanges(): boolean {
    return (
      this.formValues.name !== this.originalValues.name ||
      this.formValues.description !== this.originalValues.description
    )
  }

  /**
   * Handle form input changes
   */
  private handleInputChange(e: Event): void {
    const target = e.target as HTMLInputElement
    const { name, value } = target

    this.formValues = {
      ...this.formValues,
      [name]: value
    }
  }

  /**
   * Handle form submission
   */
  private handleSubmit(e: SubmitEvent): void {
    e.preventDefault()
    
    // Save the data
    this.saveData()
    
    // Update original values to reflect saved state
    this.originalValues = { ...this.formValues }
    
    // Close the dialog
    this.finish()
  }

  private saveData(): void {
    // Implement your save logic here
    console.log('Saving:', this.formValues)
  }

  override render(): TemplateResult {
    return html`
      ${this.renderTitle('Edit Item')}
      <form @submit="${this.handleSubmit}">
        <div>
          <label for="name">Name</label>
          <input
            type="text"
            id="name"
            name="name"
            .value="${this.formValues.name}"
            @input="${this.handleInputChange}"
            required
          />
        </div>
        <div>
          <label for="description">Description</label>
          <textarea
            id="description"
            name="description"
            .value="${this.formValues.description}"
            @input="${this.handleInputChange}"
          ></textarea>
        </div>
      </form>
      ${this.renderNegativeButton()}
      ${this.renderPositiveButton({
        label: 'Save',
        callback: () => {
          const form = this.renderRoot?.querySelector('form') as HTMLFormElement
          form?.requestSubmit()
        }
      })}
    `
  }

  /**
   * Override dialog creation to add unsaved changes detection
   */
  protected override createDialog(): void {
    this.dialog = new DialogBuilder(this)
      .setUnsavedChangesDetection({
        hasUnsavedChanges: () => this.hasUnsavedChanges(),
        warningTitle: 'Unsaved Changes',
        warningMessage: 'You have unsaved changes. Are you sure you want to discard them?',
        discardLabel: 'Discard Changes',
        keepEditingLabel: 'Keep Editing'
      })
      .open()
  }
}
```

## How It Works

1. **closing Event**: The `DialogBuilder` listens to the cancelable `closing` event from the `ui-dialog` element
2. **Change Detection**: When the dialog is about to close, it calls your `hasUnsavedChanges()` function
3. **Warning Dialog**: If there are unsaved changes, it prevents the dialog from closing and shows a warning dialog
4. **User Choice**: The user can either:
   - **Discard Changes**: Close the dialog and lose unsaved changes
   - **Keep Editing**: Return to the original dialog to continue editing

## Integration with Existing Dialogs

To add unsaved changes detection to an existing dialog, simply add the `setUnsavedChangesDetection()` call:

```typescript
// Before
const dialog = new DialogBuilder(this)
  .setTitle('My Dialog')
  .setDescription('Dialog content')
  .open()

// After
const dialog = new DialogBuilder(this)
  .setTitle('My Dialog')
  .setDescription('Dialog content')
  .setUnsavedChangesDetection({
    hasUnsavedChanges: () => this.hasUnsavedChanges()
  })
  .open()
```

## Best Practices

1. **Efficient Change Detection**: Keep your `hasUnsavedChanges()` function lightweight and fast
2. **Clear Messaging**: Use descriptive warning messages that explain what will be lost
3. **Consistent Behavior**: Apply unsaved changes detection consistently across your application
4. **User-Friendly Labels**: Use clear, action-oriented button labels
5. **State Management**: Properly track original vs. current values to detect changes accurately

## Event Flow

```
User tries to close dialog
         ↓
   Dialog fires 'closing' event
         ↓
   DialogBuilder checks hasUnsavedChanges()
         ↓
   No changes? → Allow close
         ↓
   Has changes? → Prevent close + Show warning dialog
         ↓
   User chooses "Discard Changes" → Force close dialog
   User chooses "Keep Editing" → Return to original dialog
```

## Technical Details

- The `closing` event is cancelable using `event.preventDefault()`
- The warning dialog is created using another `DialogBuilder` instance
- When discarding changes, the main dialog is force-closed by temporarily removing the `closing` event listener
- All dialogs are properly cleaned up when the parent activity is destroyed 
