"""
图操作：无向图构建、BFS 路径、有向环检测。纯函数，无 I/O。
"""
from typing import Dict, List, Optional, Set

from .transform import Transform


def build_bidirectional_graph(transforms: Dict[str, Transform]) -> Dict[str, Set[str]]:
    """
    根据 transforms 构建无向图邻接表（source<->target），用于路径查找与求逆。
    """
    graph: Dict[str, Set[str]] = {}
    for key, t in transforms.items():
        s, tgt = t.source_frame, t.target_frame
        if s not in graph:
            graph[s] = set()
        if tgt not in graph:
            graph[tgt] = set()
        graph[s].add(tgt)
        graph[tgt].add(s)
    return graph


def find_path_bfs(
    graph: Dict[str, Set[str]],
    source: str,
    target: str,
) -> Optional[List[str]]:
    """BFS 从 source 到 target 的路径，不存在返回 None。"""
    if source not in graph or target not in graph:
        return None
    queue = [(source, [source])]
    visited = {source}
    while queue:
        node, path = queue.pop(0)
        if node == target:
            return path
        for neighbor in graph.get(node, []):
            if neighbor not in visited:
                visited.add(neighbor)
                queue.append((neighbor, path + [neighbor]))
    return None


def detect_directed_cycles(transforms: Dict[str, Transform]) -> List[List[str]]:
    """
    在有向图（仅 source->target）上检测所有环，返回环列表，每个环为节点 id 列表。
    """
    directed: Dict[str, Set[str]] = {}
    all_nodes: Set[str] = set()
    for key, t in transforms.items():
        s, tgt = t.source_frame, t.target_frame
        all_nodes.add(s)
        all_nodes.add(tgt)
        if s not in directed:
            directed[s] = set()
        directed[s].add(tgt)

    cycles = []
    visited = set()
    rec_stack = set()

    def dfs(node: str, path: List[str]):
        visited.add(node)
        rec_stack.add(node)
        path.append(node)
        for neighbor in directed.get(node, []):
            if neighbor not in visited:
                dfs(neighbor, path.copy())
            elif neighbor in rec_stack:
                cycle_start = path.index(neighbor)
                cycle = path[cycle_start:] + [neighbor]
                cycles.append(cycle)
        rec_stack.remove(node)

    for node in sorted(all_nodes):
        if node not in visited:
            dfs(node, [])

    return cycles
