"""Formatters for call graph output."""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from vibelexity.models import CallGraph


def _filter_internal_calls(graph: CallGraph) -> list:
    """Return only internal calls (excluding external/built-in)."""
    return [
        c
        for c in graph.calls
        if c.callee_file is not None and c.call_type != "external"
    ]


def _normalize_filepath(filepath: str, base_path: str | Path | None) -> str:
    """Get relative path for display."""
    if not base_path:
        return filepath

    try:
        filepath_obj = Path(filepath)
        if filepath_obj.is_absolute():
            rel_path = filepath_obj.relative_to(base_path)
            return str(rel_path).replace('"', '\\"')
        return filepath
    except ValueError:
        return filepath


def _collect_all_files(graph: CallGraph, internal_calls: list) -> set[str]:
    """Collect all files with functions."""
    all_files: set[str] = set()
    for call in internal_calls:
        all_files.add(call.caller_file)
        if call.callee_file:
            all_files.add(call.callee_file)
    return all_files


def _add_cluster_lines(
    lines: list[str],
    filepath: str,
    idx: int,
    safe_path: str,
    graph: CallGraph,
) -> None:
    """Add subgraph cluster lines for a file."""
    cluster_name = f"cluster_{idx}"
    lines.append(f"    subgraph {cluster_name} {{")
    lines.append(f'        label = "{safe_path}";')

    for (func_name, func_file), func_def in graph.functions.items():
        if func_file == filepath:
            node_id = f"{filepath}:{func_name}"
            label = f"{func_name}\\n({safe_path})"
            lines.append(f'        "{node_id}" [label = "{label}"];')

    lines.append("    }")


def format_dot(graph: CallGraph, base_path: str | Path | None = None) -> str:
    """Format call graph in GraphViz DOT language with file grouping."""
    lines = ["digraph call_graph {"]
    lines.append("    node [shape=box, fontname=Arial];")
    lines.append("    edge [fontname=Arial];")

    internal_calls = _filter_internal_calls(graph)
    all_files = _collect_all_files(graph, internal_calls)

    for idx, filepath in enumerate(sorted(all_files), 1):
        safe_path = _normalize_filepath(filepath, base_path)
        _add_cluster_lines(lines, filepath, idx, safe_path, graph)

    for call in internal_calls:
        caller_id = f"{call.caller_file}:{call.caller_function}"
        callee_id = f"{call.callee_file}:{call.callee_function}"
        lines.append(f'    "{caller_id}" -> "{callee_id}";')

    lines.append("}")
    return "\n".join(lines)
