"""Import graph construction and analysis.

Builds a directed graph from parsed file imports and computes
centrality metrics for identifying hub files.
"""

from __future__ import annotations

from typing import TYPE_CHECKING

from unfamiliar.analysis.models import ImportGraph

if TYPE_CHECKING:
    from unfamiliar.analysis.adapters.protocol import AdapterRegistry
    from unfamiliar.analysis.models import FileInfo, ParsedFile


def build_import_graph(
    parsed_files: dict[str, ParsedFile],
    file_infos: dict[str, FileInfo],
    registry: AdapterRegistry,
    repo_files: set[str],
) -> ImportGraph:
    """Build a directed import graph from parsed file data.

    For each parsed file, resolves each import to a repo file using
    the adapter's resolve_import() method. Computes in-degree centrality
    for all files.

    Args:
        parsed_files: Parsed file data keyed by path.
        file_infos: File metadata keyed by path.
        registry: Adapter registry for import resolution.
        repo_files: Set of all file paths in the repository.

    Returns:
        ImportGraph with edges, files, and in_degree populated.
    """
    edges: dict[str, set[str]] = {}
    in_degree: dict[str, int] = {path: 0 for path in file_infos}

    for source_path, parsed in parsed_files.items():
        adapter = registry.get_adapter(source_path)
        if adapter is None:
            continue

        targets: set[str] = set()
        for imp in parsed.imports:
            target = adapter.resolve_import(source_path, imp.target, repo_files)
            if target and target != source_path:  # Skip self-imports
                targets.add(target)

        if targets:
            edges[source_path] = targets

            # Update in-degree for each target
            for target in targets:
                if target in in_degree:
                    in_degree[target] += 1

    return ImportGraph(
        edges=edges,
        files=file_infos,
        in_degree=in_degree,
    )


def get_hub_files(graph: ImportGraph, threshold: int = 3) -> list[str]:
    """Identify highly-connected files (imported by many others).

    Args:
        graph: The import graph.
        threshold: Minimum in-degree to be considered a hub.

    Returns:
        List of file paths sorted by in-degree descending.
    """
    hubs = [path for path, degree in graph.in_degree.items() if degree >= threshold]
    return sorted(hubs, key=lambda p: graph.in_degree[p], reverse=True)


def get_connected_components(graph: ImportGraph) -> list[set[str]]:
    """Find connected components in the undirected version of the graph.

    Uses Union-Find for efficient component detection.

    Args:
        graph: The import graph.

    Returns:
        List of sets, each containing file paths in one connected component.
    """
    # Union-Find data structure
    parent: dict[str, str] = {path: path for path in graph.files}

    def find(x: str) -> str:
        if parent[x] != x:
            parent[x] = find(parent[x])
        return parent[x]

    def union(x: str, y: str) -> None:
        px, py = find(x), find(y)
        if px != py:
            parent[px] = py

    # Union all edges (treat as undirected)
    for source, targets in graph.edges.items():
        for target in targets:
            if source in parent and target in parent:
                union(source, target)

    # Group by root
    components: dict[str, set[str]] = {}
    for path in graph.files:
        root = find(path)
        if root not in components:
            components[root] = set()
        components[root].add(path)

    return list(components.values())
