"""
Circular Loop Detector - Pure Python Implementation
Standalone version for cubesquare package.

Detects circular relationships via BFS traversal.
"""

from enum import Enum
from dataclasses import dataclass
from typing import List, Optional
from collections import deque
import math


class LoopStability(Enum):
    """Loop stability classifications based on I-Logic states."""
    STABLE = "stable"           # Converges to ABSORBED or DIRECT
    UNSTABLE = "unstable"       # Oscillates between states
    SPIRAL = "spiral"           # Diverges with phase rotation
    ANTI_SPIRAL = "anti_spiral" # Converges with reverse rotation
    UNKNOWN = "unknown"         # Cannot classify


@dataclass
class CircularRefResult:
    """Result from circular reference detection."""
    has_loop: bool
    loop_hex_ids: List[str]
    loop_length: int
    stability: LoopStability
    srl_distance: int
    detection_time_ms: float

    def to_dict(self) -> dict:
        """Convert to JSON-serializable dict."""
        return {
            'has_loop': self.has_loop,
            'loop_hex_ids': self.loop_hex_ids,
            'loop_length': self.loop_length,
            'stability': self.stability.value,
            'srl_distance': self.srl_distance,
            'detection_time_ms': self.detection_time_ms,
        }


class CircularLoopDetector:
    """
    Circular reference detector using BFS.

    Performance: O(V+E) where V=vertices, E=edges
    """

    def __init__(self):
        """Initialize detector."""
        self._graph_data = {'adjacency': {}, 'hex_ids': set()}

    def load_graph(self, relationships: List[dict]):
        """
        Load relationship graph.

        Args:
            relationships: List of {source_hex, target_hex, rel_type, strength}
        """
        self._graph_data = {'adjacency': {}, 'hex_ids': set()}
        for rel in relationships:
            src = rel['source_hex']
            tgt = rel['target_hex']
            self._graph_data['hex_ids'].add(src)
            self._graph_data['hex_ids'].add(tgt)
            if src not in self._graph_data['adjacency']:
                self._graph_data['adjacency'][src] = []
            self._graph_data['adjacency'][src].append(tgt)

    def detect_loop(self, start_hex_id: str, max_depth: int = 100) -> CircularRefResult:
        """
        Detect circular reference starting from hex ID.

        Args:
            start_hex_id: Starting hex ID (AACCLLII format)
            max_depth: Maximum BFS depth

        Returns:
            CircularRefResult with loop info
        """
        import time
        start_time = time.perf_counter()

        loop_hex_ids = self._detect_cycle(start_hex_id, max_depth)
        elapsed_ms = (time.perf_counter() - start_time) * 1000

        if not loop_hex_ids:
            return CircularRefResult(
                has_loop=False, loop_hex_ids=[], loop_length=0,
                stability=LoopStability.STABLE, srl_distance=0,
                detection_time_ms=elapsed_ms
            )

        stability = self._classify_stability(loop_hex_ids)
        srl_dist = int(math.isqrt(len(loop_hex_ids))) if len(loop_hex_ids) > 0 else 0

        return CircularRefResult(
            has_loop=True, loop_hex_ids=loop_hex_ids,
            loop_length=len(loop_hex_ids), stability=stability,
            srl_distance=srl_dist, detection_time_ms=elapsed_ms
        )

    def _detect_cycle(self, start_hex: str, max_depth: int) -> List[str]:
        """BFS cycle detection with path tracking."""
        if start_hex not in self._graph_data.get('adjacency', {}):
            return []

        # Self-loop check
        if start_hex in self._graph_data['adjacency'].get(start_hex, []):
            return [start_hex, start_hex]

        visited = {start_hex: [start_hex]}
        queue = deque([start_hex])
        depth = 0

        while queue and depth < max_depth:
            level_size = len(queue)
            for _ in range(level_size):
                node = queue.popleft()
                for neighbor in self._graph_data['adjacency'].get(node, []):
                    if neighbor == start_hex and len(visited[node]) > 1:
                        return visited[node] + [start_hex]
                    if neighbor not in visited:
                        visited[neighbor] = visited[node] + [neighbor]
                        queue.append(neighbor)
            depth += 1

        return []

    def _classify_stability(self, loop_hex_ids: List[str]) -> LoopStability:
        """Classify loop stability using I-Logic states."""
        if not loop_hex_ids:
            return LoopStability.STABLE

        first_area = int(loop_hex_ids[0][:2], 16) if loop_hex_ids[0][:2].isalnum() else 0
        last_area = int(loop_hex_ids[-1][:2], 16) if loop_hex_ids[-1][:2].isalnum() else 0

        if first_area == 0 or last_area == 0:
            return LoopStability.STABLE

        if abs(first_area - last_area) < 5:
            return LoopStability.UNSTABLE

        loop_length = len(loop_hex_ids)
        PHI = 1.618033988749895
        phi_ratio = (PHI ** loop_length) / loop_length if loop_length > 0 else 1.0

        if phi_ratio > 1.618:
            return LoopStability.SPIRAL
        elif phi_ratio < 0.618:
            return LoopStability.ANTI_SPIRAL

        return LoopStability.STABLE
