"""Core tree-sitter utilities shared by all TS/JS adapter modules

Provides tree traversal, text extraction, and complexity calculation.
Import-specific functions are in _ts_imports.py.
Definition extraction is in _ts_definitions.py.
Entry point detection is in _ts_entry_points.py.
"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from collections.abc import Generator, Sequence

    from tree_sitter import Node


def traverse(root: Node) -> Generator[Node, None, None]:
    """Depth-first traversal of a tree-sitter parse tree.

    Uses TreeCursor for iterative traversal (no stack overflow on deep trees).

    Args:
        root: The root node of the parse tree.

    Yields:
        Each node in the tree exactly once, root first.
    """
    cursor = root.walk()
    while True:
        node = cursor.node
        if node is None:
            return  # Safety check - should never happen with valid cursor
        yield node
        if cursor.goto_first_child() or cursor.goto_next_sibling():
            continue
        while True:
            if not cursor.goto_parent():
                return
            if cursor.goto_next_sibling():
                break


def get_node_text(node: Node) -> str:
    """Safely get text content from a node."""
    if node is None:
        return ""
    text = node.text
    if text is None:
        return ""
    return text.decode("utf-8", errors="replace")


def calculate_complexity(root: Node) -> int:
    """Calculate approximate cyclomatic complexity for JS/TS.

    Counts branching nodes:
    - if_statement
    - for_statement, for_in_statement, for_of_statement
    - while_statement, do_statement
    - switch_case
    - catch_clause
    - ternary_expression
    - && and || binary expressions

    Args:
        root: The root node of the parsed JS/TS file.

    Returns:
        Cyclomatic complexity score (>= 0).
    """
    complexity = 0
    branching_types = {
        "if_statement",
        "for_statement",
        "for_in_statement",
        "for_of_statement",
        "while_statement",
        "do_statement",
        "switch_case",
        "catch_clause",
        "ternary_expression",
    }

    for node in traverse(root):
        if node.type in branching_types:
            complexity += 1
        elif node.type == "binary_expression":
            # Count && and ||
            op = node.child_by_field_name("operator")
            if op:
                op_text = get_node_text(op)
                if op_text in ("&&", "||"):
                    complexity += 1

    return complexity


def resolve_js_ts_import(
    importing_file: str,
    import_target: str,
    repo_files: set[str],
    extensions: Sequence[str],
) -> str | None:
    """Resolve a JS/TS import to a file path in the repo

    Handles:
    - Relative imports (.foo, ../bar)
    - Extension-less imports (tries extensions in order)
    - Index files (tries index.ext for each extension)

    Bare imports (no . prefix) are treated as external packages.

    Args:
        importing_file: Path of the file containing the import.
        import_target: The raw import string (e.g., "./utils", "../helpers").
        repo_files: Set of file paths in the repository.
        extensions: Sequence of file extensions to try in preference order.

    Returns:
        Resolved file path relative to repo root or None for external imports.
    """

    # Bare imports are external packages
    if not import_target.startswith("."):
        return None

    # Resolve relative path, correctly handling both ./ and ../ segments.
    # Path's / operator handles ./ stripping; we normalize .. manually to
    # avoid os.path.normpath (which is the only stdlib option but adds a dep).
    raw = Path(importing_file).parent / import_target
    parts: list[str] = []
    for part in raw.parts:
        if part == "..":
            if parts:
                parts.pop()
        elif part != ".":
            parts.append(part)
    resolved = "/".join(parts) if parts else ""

    # Build candidate paths to try
    candidates = _build_js_ts_candidates("", resolved, extensions)

    for candidate in candidates:
        if candidate in repo_files:
            return candidate

        # Support src/ layout and other directory-prefixed layouts
        suffix = "/" + candidate
        for repo_file in repo_files:
            if repo_file.endswith(suffix):
                return repo_file

    return None


def _build_js_ts_candidates(
    importing_dir: str, target: str, extensions: Sequence[str]
) -> list[str]:
    """Build list of candidate file paths to try for JS/TS import resolution."""

    candidates: list[str] = []

    # Build base path
    base = f"{importing_dir}/{target}" if importing_dir else target

    # Try direct file with each extension
    for ext in extensions:
        candidates.append(f"{base}{ext}")

    # Try index files in subdirectory
    for ext in extensions:
        candidates.append(f"{base}/index{ext}")

    return candidates
