"""Call graph extraction from tree-sitter AST.

Walks function bodies to find call expressions and builds
caller -> callee edges for the call graph.
"""

from .symbols import CallEdge
from .languages import LanguageSpec


def extract_calls(
    node,
    spec: LanguageSpec,
    source_bytes: bytes,
) -> list[CallEdge]:
    """Extract call edges from a function/method body.

    Walks all descendants of the given node looking for call expressions
    as defined by the language spec.

    Args:
        node: Tree-sitter node (typically a function body).
        spec: Language specification.
        source_bytes: Raw source for text extraction.

    Returns:
        List of CallEdge objects found within the node.
    """
    calls: list[CallEdge] = []
    _walk_for_calls(node, spec, source_bytes, calls)
    return calls


def _walk_for_calls(
    node,
    spec: LanguageSpec,
    source_bytes: bytes,
    calls: list[CallEdge],
) -> None:
    """Recursively walk AST looking for call expressions."""
    if node.type in spec.call_node_types:
        edge = _extract_call_edge(node, spec, source_bytes)
        if edge:
            calls.append(edge)

    for child in node.children:
        _walk_for_calls(child, spec, source_bytes, calls)


def _extract_call_edge(
    node,
    spec: LanguageSpec,
    source_bytes: bytes,
) -> CallEdge | None:
    """Extract a CallEdge from a call expression node."""
    callee_name = _extract_callee_name(node, spec, source_bytes)
    if not callee_name:
        return None

    return CallEdge(
        callee_name=callee_name,
        line=node.start_point[0] + 1,
    )


def _extract_callee_name(
    node,
    spec: LanguageSpec,
    source_bytes: bytes,
) -> str | None:
    """Extract the callee function/method name from a call node.

    Handles patterns like:
        foo()           -> "foo"
        obj.method()    -> "obj.method"
        pkg::func()     -> "pkg::func"
        self.method()   -> "method" (strip self/this)
    """
    # Try the callee field from spec
    callee = node.child_by_field_name(spec.callee_field)
    if not callee:
        # Some grammars put the callee as first child
        if node.child_count > 0:
            callee = node.children[0]
        else:
            return None

    text = _node_text(callee, source_bytes).strip()
    if not text:
        return None

    # Strip self./this. prefix for cleaner graph
    for prefix in ('self.', 'this.', 'this->'):
        if text.startswith(prefix):
            text = text[len(prefix):]
            break

    # Skip overly complex expressions (lambdas, computed calls)
    if '(' in text or '{' in text or '\n' in text:
        return None

    return text


def _node_text(node, source_bytes: bytes) -> str:
    """Get the text of a tree-sitter node."""
    return source_bytes[node.start_byte:node.end_byte].decode('utf-8', errors='replace')
