# DetectCircularReference

## Overview

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

## Business Rules

- A node cannot be moved to become a descendant of itself
- Self-referencing is explicitly rejected: newParentId === nodeId
- Ancestor chain is traversed from the new parent upward to the root
- If the moved node's ID is found in the ancestor chain, the move is circular
- Traversal stops at root (parentId = 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 nodeId + newParentId] --> B{newParentId === nodeId?}
    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.parentId]
    H --> I{ancestorId === nodeId?}
    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 node itself or one of its descendants
- **SELF_REFERENCE**: Node is moved to itself as parent — returns isCircular: true

## Test Cases

- detects self-reference as circular
- detects moving node under its child as circular
- detects moving node 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)
