# DetectCircularReference

## Overview

DetectCircularReference validates that reparenting an account group would not create a circular reference in the tree hierarchy. It walks up the ancestor chain from the proposed new parent, checking whether the group being moved appears as an ancestor. If the moved group is found in the ancestor chain, the move would create a cycle.

## Business Rules

- Accepts `groupId` (the group being moved) and `newParentId` (the proposed new parent)
- Returns `{ isCircular: boolean }` — true if the move would create a cycle, false otherwise
- A group cannot be moved to become a descendant of itself
- Self-referencing is explicitly rejected: newParentId === groupId
- Ancestor chain is traversed from the new parent upward to the root
- If the moved group's ID is found in the ancestor chain, the move is circular
- Traversal stops at root (parentAccountGroupId = null) or broken chain (parent not found)
- Only relevant when newParentId is non-null (promoting to root cannot create a cycle)

## Process Flow

```mermaid
flowchart TD
    A[Receive groupId + newParentId] --> B{newParentId === groupId?}
    B -->|Yes| C[Return isCircular: true]
    B -->|No| D[Set ancestorId = newParentId]
    D --> E[SELECT ancestor where id = ancestorId]
    E --> F{Ancestor found?}
    F -->|No| G[Return isCircular: false]
    F -->|Yes| H[Set ancestorId = ancestor.parentAccountGroupId]
    H --> I{ancestorId === groupId?}
    I -->|Yes| C
    I -->|No| J{ancestorId is null?}
    J -->|Yes| G
    J -->|No| E
```

## External Dependencies

- None

## Error Scenarios

- **CIRCULAR_REFERENCE**: New parent is the group itself or one of its descendants
- **SELF_REFERENCE**: Group is moved to itself as parent — returns isCircular: true

## Test Cases

- detects self-reference as circular
- detects moving group under its child as circular
- detects moving group under its grandchild as circular
- returns not circular for valid move
- returns not circular when new parent is root
- handles broken ancestor chain gracefully (not circular)
