"""Lazy-loading tree-sitter runtime for codectx parsers.

Supported grammars: go, javascript, typescript, tsx, java, rust.
Falls back gracefully (returns None/None) when a grammar package is absent.
"""

from __future__ import annotations

import threading
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from tree_sitter import Language, Node, Parser

_lock = threading.Lock()
_langs: dict[str, "Language"] = {}
_parsers: dict[str, "Parser"] = {}
_tried: dict[str, bool] = {}


def _load(name: str) -> bool:
    """Try once to load the tree-sitter grammar for *name*. Results are cached."""
    with _lock:
        if name in _tried:
            return _tried[name]
        try:
            from tree_sitter import Language as _Lang, Parser as _TSParser

            if name == "go":
                import tree_sitter_go as _m
                lang = _Lang(_m.language())
            elif name == "javascript":
                import tree_sitter_javascript as _m
                lang = _Lang(_m.language())
            elif name == "typescript":
                import tree_sitter_typescript as _m
                fn = getattr(_m, "language_typescript", getattr(_m, "language", None))
                lang = _Lang(fn())
            elif name == "tsx":
                import tree_sitter_typescript as _m
                fn = getattr(_m, "language_tsx", getattr(_m, "language", None))
                lang = _Lang(fn())
            elif name == "java":
                import tree_sitter_java as _m
                lang = _Lang(_m.language())
            elif name == "rust":
                import tree_sitter_rust as _m
                lang = _Lang(_m.language())
            else:
                _tried[name] = False
                return False

            # Support both 0.22+ API (Parser(lang)) and 0.21 API (Parser(); .set_language())
            try:
                p = _TSParser(lang)
            except TypeError:
                p = _TSParser()
                p.set_language(lang)  # type: ignore[attr-defined]

            _langs[name] = lang
            _parsers[name] = p
            _tried[name] = True
            return True
        except Exception:
            _tried[name] = False
            return False


def get_parser(name: str) -> "tuple[Parser, Language] | tuple[None, None]":
    """Return (parser, language) or (None, None) when unavailable."""
    if not _load(name):
        return None, None
    return _parsers[name], _langs[name]


def node_text(node: "Node", src: bytes) -> str:
    """Decode the source slice covered by *node*."""
    return src[node.start_byte:node.end_byte].decode("utf-8", errors="replace")


def start_line(node: "Node") -> int:
    """1-indexed start line of *node*."""
    return node.start_point[0] + 1


def end_line(node: "Node") -> int:
    """1-indexed end line of *node*."""
    return node.end_point[0] + 1


def walk_type(root: "Node", *node_types: str):
    """Iterative depth-first walk — yields nodes whose type is in *node_types*."""
    stack = [root]
    while stack:
        node = stack.pop()
        if node.type in node_types:
            yield node
        for child in reversed(node.children):
            stack.append(child)
