"""parse_code_ast — extrae entidades de codigo fuente usando tree-sitter."""

from __future__ import annotations

import sys
import os

# Permite importar CodeEntity desde python/types/core/
_TYPES_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "..", "python", "types", "core")
_TYPES_DIR = os.path.abspath(_TYPES_DIR)
if _TYPES_DIR not in sys.path:
    sys.path.insert(0, _TYPES_DIR)

try:
    from code_entity import CodeEntity
except ImportError:
    # Fallback: define localmente para que el modulo pueda importarse
    from dataclasses import dataclass, field

    @dataclass
    class CodeEntity:  # type: ignore[no-redef]
        kind: str
        name: str
        start_line: int
        end_line: int
        signature: str
        docstring: str | None
        language: str
        children: list["CodeEntity"] = field(default_factory=list)


# ---------------------------------------------------------------------------
# Lenguajes soportados
# ---------------------------------------------------------------------------

SUPPORTED_LANGUAGES: dict[str, str] = {
    "python": "tree_sitter_python",
    "javascript": "tree_sitter_javascript",
    "typescript": "tree_sitter_typescript",
    "go": "tree_sitter_go",
    "rust": "tree_sitter_rust",
    "java": "tree_sitter_java",
    "cpp": "tree_sitter_cpp",
}

# Alias normalizados
_ALIASES: dict[str, str] = {
    "py": "python",
    "js": "javascript",
    "ts": "typescript",
    "rs": "rust",
    "c++": "cpp",
    "cxx": "cpp",
}

# Nodos AST relevantes por lenguaje
_ENTITY_NODES: dict[str, dict[str, str]] = {
    "python": {
        "function_definition": "function",
        "class_definition": "class",
        "decorated_definition": "function",  # resuelto en runtime
    },
    "javascript": {
        "function_declaration": "function",
        "function_expression": "function",
        "arrow_function": "function",
        "class_declaration": "class",
        "method_definition": "method",
    },
    "typescript": {
        "function_declaration": "function",
        "function_expression": "function",
        "arrow_function": "function",
        "class_declaration": "class",
        "method_definition": "method",
        "interface_declaration": "interface",
        "type_alias_declaration": "type",
    },
    "go": {
        "function_declaration": "function",
        "method_declaration": "method",
        "type_declaration": "type",
    },
    "rust": {
        "function_item": "function",
        "struct_item": "struct",
        "impl_item": "class",  # impl block tratado como class con metodos
        "trait_item": "trait",
    },
    "java": {
        "class_declaration": "class",
        "method_declaration": "method",
        "interface_declaration": "interface",
        "constructor_declaration": "function",
    },
    "cpp": {
        "function_definition": "function",
        "class_specifier": "class",
        "template_declaration": "function",
        "struct_specifier": "struct",
    },
}


# ---------------------------------------------------------------------------
# Helpers de extraccion
# ---------------------------------------------------------------------------

def _node_text(node, source_bytes: bytes) -> str:
    """Retorna el texto crudo del nodo."""
    return source_bytes[node.start_byte:node.end_byte].decode("utf-8", errors="replace")


def _extract_name(node, source_bytes: bytes, lang: str) -> str:
    """Extrae el nombre de la entidad desde el nodo AST."""
    # Buscar nodo hijo con type 'identifier' o 'name'
    for child in node.children:
        if child.type in ("identifier", "name", "type_identifier", "field_identifier"):
            return _node_text(child, source_bytes)
    # Para decorated_definition en Python, ir al nodo interno
    if node.type == "decorated_definition":
        for child in node.children:
            if child.type in ("function_definition", "class_definition"):
                return _extract_name(child, source_bytes, lang)
    return "<anonymous>"


def _extract_signature(node, source_bytes: bytes, lang: str) -> str:
    """Extrae la firma del nodo: todo antes del body."""
    body_types = {
        "block", "body", "statement_block", "compound_statement",
        "declaration_list", "field_declaration_list", "class_body",
        "enum_body", "struct_body", "impl_body",
    }
    # Acumular texto hasta encontrar el body
    start = node.start_byte
    body_start = None
    for child in node.children:
        if child.type in body_types:
            body_start = child.start_byte
            break
    if body_start is not None:
        sig = source_bytes[start:body_start].decode("utf-8", errors="replace").strip()
    else:
        # Sin body distinguible (ej: type alias, interface en algunos grammars)
        sig = _node_text(node, source_bytes).split("\n")[0].strip()
    return sig


def _extract_docstring_python(node, source_bytes: bytes) -> str | None:
    """Extrae docstring Python: primer string literal en el body."""
    body_types = {"block", "body"}
    for child in node.children:
        if child.type in body_types:
            for stmt in child.children:
                if stmt.type == "expression_statement":
                    for inner in stmt.children:
                        if inner.type in ("string", "concatenated_string"):
                            raw = _node_text(inner, source_bytes)
                            # Limpiar comillas triples / simples
                            for q in ('"""', "'''", '"', "'"):
                                if raw.startswith(q) and raw.endswith(q) and len(raw) > 2 * len(q):
                                    return raw[len(q):-len(q)].strip()
                            return raw.strip()
                # Solo miramos el primer statement real
                if stmt.type not in ("comment", "\n", "pass_statement"):
                    break
    return None


def _extract_docstring_comment(node, source_bytes: bytes, all_bytes: bytes) -> str | None:
    """Extrae comentario previo al nodo (para Go, Rust, Java, C++)."""
    # Buscar comentario en siblings anteriores del nodo en el tree
    # Usamos el inicio del nodo y buscamos hacia atras en el texto
    start = node.start_byte
    preceding = all_bytes[:start].decode("utf-8", errors="replace")
    lines = preceding.split("\n")
    # Recolectar lineas de comentario inmediatamente anteriores
    comment_lines: list[str] = []
    for line in reversed(lines):
        stripped = line.strip()
        if stripped.startswith("///") or stripped.startswith("//"):
            comment_lines.insert(0, stripped.lstrip("/").strip())
        elif stripped.startswith("*") or stripped.startswith("/**") or stripped.startswith("*/"):
            content = stripped.lstrip("/*").rstrip("*/").strip()
            if content:
                comment_lines.insert(0, content)
        elif stripped == "":
            # Linea en blanco: detener busqueda solo si ya tenemos algo
            if comment_lines:
                break
        else:
            break
    return "\n".join(comment_lines) if comment_lines else None


def _extract_docstring(node, source_bytes: bytes, lang: str) -> str | None:
    """Extrae docstring/comentario segun el lenguaje."""
    if lang == "python":
        # Para decorated_definition, extraer del nodo interno
        if node.type == "decorated_definition":
            for child in node.children:
                if child.type in ("function_definition", "class_definition"):
                    return _extract_docstring_python(child, source_bytes)
        return _extract_docstring_python(node, source_bytes)
    else:
        return _extract_docstring_comment(node, source_bytes, source_bytes)


def _resolve_kind(node_type: str, node, source_bytes: bytes, lang: str, entity_map: dict) -> str:
    """Resuelve el kind de la entidad (maneja decorated_definition en Python)."""
    base_kind = entity_map.get(node_type, "function")
    if node_type == "decorated_definition":
        for child in node.children:
            if child.type == "class_definition":
                return "class"
            if child.type == "function_definition":
                return "function"
    return base_kind


def _extract_children(node, source_bytes: bytes, lang: str, entity_map: dict) -> list[CodeEntity]:
    """Extrae metodos/funciones hijos dentro de una clase/impl/trait."""
    children: list[CodeEntity] = []
    method_kinds = {
        "method_definition", "method_declaration", "function_definition",
        "function_declaration", "function_item",
    }
    body_types = {
        "block", "body", "statement_block", "class_body", "declaration_list",
        "impl_body", "field_declaration_list",
    }

    def _walk_for_methods(n):
        for child in n.children:
            if child.type in body_types:
                _walk_for_methods(child)
            elif child.type in method_kinds and child.type in entity_map:
                kind = entity_map[child.type]
                name = _extract_name(child, source_bytes, lang)
                start_line = child.start_point[0] + 1
                end_line = child.end_point[0] + 1
                sig = _extract_signature(child, source_bytes, lang)
                doc = _extract_docstring(child, source_bytes, lang)
                children.append(CodeEntity(
                    kind=kind,
                    name=name,
                    start_line=start_line,
                    end_line=end_line,
                    signature=sig,
                    docstring=doc,
                    language=lang,
                    children=[],
                ))

    _walk_for_methods(node)
    return children


# ---------------------------------------------------------------------------
# Funcion principal
# ---------------------------------------------------------------------------

def parse_code_ast(source_code: str, language: str) -> list[CodeEntity]:
    """Extrae entidades de codigo fuente usando tree-sitter.

    Parsea el codigo fuente y retorna una lista plana de entidades de nivel
    superior (funciones, clases, tipos, interfaces). Las clases incluyen sus
    metodos como `children`.

    Args:
        source_code: Codigo fuente como string.
        language: Lenguaje del codigo. Valores soportados: "python",
            "javascript", "typescript", "go", "rust", "java", "cpp".
            Se aceptan alias: "py", "js", "ts", "rs", "c++", "cxx".

    Returns:
        Lista de CodeEntity con las entidades extraidas. Lista vacia si el
        codigo no contiene entidades reconocibles o es vacio.

    Raises:
        ValueError: Si el lenguaje no esta soportado.
        ImportError: Si tree-sitter o el grammar del lenguaje no estan
            instalados. Instalar con:
            `pip install tree-sitter tree-sitter-python tree-sitter-go` etc.
    """
    if not source_code or not source_code.strip():
        return []

    # Normalizar lenguaje
    lang = language.lower().strip()
    lang = _ALIASES.get(lang, lang)

    if lang not in SUPPORTED_LANGUAGES:
        supported = ", ".join(sorted(SUPPORTED_LANGUAGES))
        raise ValueError(
            f"Lenguaje '{language}' no soportado. "
            f"Soportados: {supported}"
        )

    # Importar tree-sitter
    try:
        import tree_sitter  # noqa: F401
        from tree_sitter import Language, Parser
    except ImportError as exc:
        raise ImportError(
            "tree-sitter no esta instalado. Instalar con: "
            "pip install tree-sitter tree-sitter-python tree-sitter-go "
            "tree-sitter-javascript tree-sitter-typescript tree-sitter-rust "
            "tree-sitter-java tree-sitter-cpp"
        ) from exc

    # Importar grammar del lenguaje
    grammar_module = SUPPORTED_LANGUAGES[lang]
    try:
        import importlib
        mod = importlib.import_module(grammar_module)
    except ImportError as exc:
        pip_name = grammar_module.replace("_", "-")
        raise ImportError(
            f"Grammar '{grammar_module}' no instalado. Instalar con: "
            f"pip install {pip_name}"
        ) from exc

    # Construir Language segun la API de tree-sitter >= 0.22
    try:
        # API nueva: el modulo tiene una funcion language() que retorna el Language
        if lang == "typescript":
            # tree-sitter-typescript expone typescript y tsx por separado
            ts_lang = Language(mod.language_typescript())
        elif hasattr(mod, "language"):
            ts_lang = Language(mod.language())
        else:
            # Fallback: algunos grammars exponen Language directamente
            ts_lang = Language(mod.Language)
    except Exception as exc:
        raise ImportError(
            f"No se pudo inicializar el grammar para '{lang}': {exc}. "
            f"Verifica que {grammar_module} es compatible con esta version de tree-sitter."
        ) from exc

    parser = Parser(ts_lang)

    source_bytes = source_code.encode("utf-8")
    tree = parser.parse(source_bytes)

    entity_map = _ENTITY_NODES.get(lang, {})
    top_level_kinds = {"class", "struct", "trait", "interface"}

    results: list[CodeEntity] = []

    def _walk(node, depth: int = 0) -> None:
        """Walk recursivo del AST para extraer entidades de nivel superior."""
        if node.type in entity_map and depth <= 2:
            kind = _resolve_kind(node.type, node, source_bytes, lang, entity_map)
            name = _extract_name(node, source_bytes, lang)
            start_line = node.start_point[0] + 1
            end_line = node.end_point[0] + 1
            sig = _extract_signature(node, source_bytes, lang)
            doc = _extract_docstring(node, source_bytes, lang)

            # Extraer children solo para contenedores
            children: list[CodeEntity] = []
            if kind in top_level_kinds:
                children = _extract_children(node, source_bytes, lang, entity_map)

            results.append(CodeEntity(
                kind=kind,
                name=name,
                start_line=start_line,
                end_line=end_line,
                signature=sig,
                docstring=doc,
                language=lang,
                children=children,
            ))
            # No seguir descendiendo si ya extrajimos la entidad (children ya incluidos)
            return

        for child in node.children:
            _walk(child, depth + 1)

    _walk(tree.root_node)
    return results
