# AUR0505

## Error Message

`AUR0505: Controller at <ControllerName> is in an unexpected state: <State> during deactivation.`

Where:

* `<ControllerName>` is the name of the controller.
* `<State>` is the unexpected state the controller was found in (e.g., `created`, `activating`, `deactivating`, `disposed`).

## Description

This error occurs during the deactivation phase of a component's lifecycle when Aurelia finds that the controller is not in one of the expected states (`bound` or `attached`). Deactivation should only proceed from these states; finding the controller in another state indicates a potential lifecycle mismatch or error in managing the component's state.

## Cause

This error often mirrors the causes of `AUR0503` but in the context of deactivation:

1. **Re-entrant Deactivation:** An attempt to deactivate a controller that is already in the process of deactivating or activating. This can happen with complex asynchronous operations or event handling within lifecycle methods.
2. **Lifecycle Interference:** Custom code directly manipulating the controller's state or calling lifecycle methods (like `deactivate`) out of the expected sequence.
3. **Concurrency Issues:** Multiple asynchronous operations trying to manipulate the same controller's lifecycle concurrently without proper coordination, leading to attempts to deactivate from an invalid state.
4. **Incomplete Activation:** If the activation process failed or was interrupted, the controller might be left in an intermediate state (e.g., `created`), and a subsequent deactivation attempt would then fail because it didn't reach `bound` or `attached`.
5. **Framework Bug (Less Common):** In rare cases, an internal issue within the Aurelia framework could lead to inconsistent state management.

## Solution

1. **Analyze Deactivation Triggers:** Determine what triggers the deactivation. Is it standard routing, removal via `if.bind`, `repeat.for`, or custom code manually calling `controller.deactivate()`?
2. **Simplify Lifecycle Logic:** Review the component's lifecycle hooks (`activate`, `binding`, `bound`, `attaching`, `detaching`, `unbinding`). Ensure they don't cause re-entrant deactivations or interfere with the expected state transitions. Pay close attention to asynchronous operations initiated in activation/attachment hooks and ensure they are properly handled or cancelled during deactivation/detachment hooks (`detaching`, `unbinding`).
3. **Ensure Proper Activation:** Verify that the component activates correctly and reaches the `attached` state before any deactivation attempt occurs. Fix any errors preventing successful activation.
4. **Synchronize Concurrent Operations:** If multiple asynchronous tasks can affect the component's lifecycle, ensure proper synchronization or state checking.
5. **Avoid Manual State/Lifecycle Calls:** Do not manually set `controller.state` or call `controller.deactivate` unless absolutely necessary and the implications are fully understood. Let Aurelia manage the lifecycle.
6. **Isolate the Problem:** Simplify the component and the scenario leading to the error to pinpoint the cause.

## Example

```typescript
import { customElement, ICustomElementController } from 'aurelia';

@customElement({ name: 'another-complex-lifecycle', template: '...' })
export class AnotherComplexLifecycle {
  public controller!: ICustomElementController<this>;
  private ongoingOperation: Promise<void> | null = null;
  private isAttached = false;

  attached() {
    this.isAttached = true;
    // Start some operation that might interact with state
    this.ongoingOperation = this.doSomethingAsync();
  }

  async detaching(initiator: ICustomElementController | null) {
    this.isAttached = false;
    console.log('Detaching...');

    // --- Potentially Problematic ---
    // If doSomethingAsync() somehow triggered a deactivate *again* on this
    // same controller while it's already in the detaching/deactivating phase,
    // it could lead to AUR0505.
    // Example:
    // if (this.ongoingOperation) {
    //   await this.ongoingOperation;
    //   // Imagine this tries to clean up by calling deactivate again:
    //   console.log('Operation finished, trying to ensure deactivation...');
    //   // Calling deactivate manually here is risky and likely wrong
    //   await this.controller.deactivate(initiator ?? this.controller, this.controller.parentController); // DANGEROUS
    // }
    // -----------------------------

    // --- Safer ---
    // Wait for ongoing operations if necessary, but let Aurelia handle the state transitions.
    // If cancellation is needed, handle it here without calling deactivate again.
    try {
        await this.ongoingOperation;
        console.log('Ongoing operation completed during detach.');
    } catch (e) {
        console.error('Error during ongoing operation cleanup:', e);
    }
  }

  async doSomethingAsync() {
    await new Promise(resolve => setTimeout(resolve, 200));
    if (!this.isAttached) {
        console.log('Operation finished after detach.');
        return;
    }
    console.log('Async operation completed while attached.');
    // Avoid triggering lifecycle changes from here without careful state checking.
  }
}

// --- External Code Example (Problematic) ---
// function triggerExternalDeactivation(controller: ICustomElementController) {
//   // If this is called when the controller is not 'bound' or 'attached'
//   // (e.g., already deactivating, or maybe never fully activated), it could cause AUR0505.
//   console.log(`Externally triggering deactivation for ${controller.name}`);
//   controller.deactivate(controller, controller.parentController)
//     .catch(err => console.error("External deactivation failed:", err)); // Might log AUR0505
// }
```

## Debugging Tips

* Identify the `<ControllerName>` and the unexpected `<State>` from the error message.
* Set breakpoints at the beginning of the `Controller#deactivate` method in `templating/controller.ts` and in the component's own deactivation hooks (`detaching`, `unbinding`). Inspect `controller.state`.
* Trace the call stack when the error occurs to understand what initiated the deactivation.
* Examine the component's activation sequence. Did it complete successfully and reach the `attached` state? Log the state in each lifecycle hook.
* Look for asynchronous operations started during activation/attachment. Are they properly handled or cancelled during deactivation/detachment? Could a delayed callback be attempting actions on a controller that's already deactivating?
* Search for manual calls to `controller.deactivate` or manipulation of `controller.state`.
