"""C++ language parser using tree-sitter."""

from __future__ import annotations

from typing import Any

import tree_sitter_cpp
from tree_sitter import Node

from viper.languages.base import Function, TreeSitterParser


class CppTreeSitterParser(TreeSitterParser):
    """Tree-sitter based parser for the C++ language."""

    language_name = "cpp"
    file_extensions = [".cpp", ".cxx", ".cc", ".hpp", ".hxx", ".hh"]

    @staticmethod
    def _get_ts_language() -> Any:
        return tree_sitter_cpp.language()

    @staticmethod
    def _get_function_query() -> str:
        return "(function_definition) @func"

    def _extract_function(self, node: Node, code: bytes) -> Function:
        func = Function()
        func.start = node.start_point[0] + 1
        func.end = node.end_point[0] + 1

        # Try the standard query first (functions with a return type)
        detail_query = self._get_detail_query()
        captures = self.capture_nodes(node, detail_query)

        if captures:
            for key, nodes in captures.items():
                a = nodes[0]
                val = code[a.start_byte:a.end_byte].decode("utf-8")
                if key == "name":
                    func.name = val
                    func.line = a.start_point[0] + 1
                elif key == "return_type":
                    func.return_type = val
                elif key == "signature":
                    func.signature = val
        else:
            # Fallback: constructor / destructor (no return type)
            ctor_query = self._get_ctor_dtor_detail_query()
            for key, nodes in self.capture_nodes(node, ctor_query).items():
                a = nodes[0]
                val = code[a.start_byte:a.end_byte].decode("utf-8")
                if key == "name":
                    func.name = val
                    func.line = a.start_point[0] + 1
                elif key == "dtor_name":
                    func.name = val
                    func.line = a.start_point[0] + 1
                elif key == "signature":
                    func.signature = val

        # Normalise multi-line signatures
        if "\n" in func.signature:
            func.signature = " ".join(func.signature.split())

        return func

    @staticmethod
    def _get_detail_query() -> str:
        """Query to extract name, return type, signature from a C++ function node.

        Handles both free functions and qualified (Class::method) definitions.
        """
        return """
        (function_definition
            type: (_) @return_type
            declarator: [
                (function_declarator
                    declarator: (identifier) @name
                    parameters: (_) @signature
                )
                (function_declarator
                    declarator: (qualified_identifier) @name
                    parameters: (_) @signature
                )
                (function_declarator
                    declarator: (field_identifier) @name
                    parameters: (_) @signature
                )
            ]
        )
        """

    @staticmethod
    def _get_ctor_dtor_detail_query() -> str:
        """Query to extract constructor/destructor name and signature.

        These function_definitions have no return type node.
        """
        return """
        (function_definition
            declarator: [
                (function_declarator
                    declarator: (identifier) @name
                    parameters: (_) @signature
                )
                (function_declarator
                    declarator: (field_identifier) @name
                    parameters: (_) @signature
                )
                (function_declarator
                    declarator: (destructor_name) @dtor_name
                    parameters: (_) @signature
                )
            ]
        )
        """

    def _get_decision_node_types(self) -> set[str]:
        return {
            "if_statement",
            "for_statement",
            "for_range_loop",
            "while_statement",
            "do_statement",
            "case_statement",
            "catch_clause",
            "conditional_expression",
            "&&",
            "||",
        }

    def _get_modified_decision_node_types(self) -> set[str]:
        return {
            "if_statement",
            "for_statement",
            "for_range_loop",
            "while_statement",
            "do_statement",
            "switch_statement",
            "catch_clause",
            "conditional_expression",
            "&&",
            "||",
        }

    def _get_statement_node_types(self) -> set[str]:
        return {
            "expression_statement",
            "declaration",
            "return_statement",
            "if_statement",
            "for_statement",
            "for_range_loop",
            "while_statement",
            "do_statement",
            "switch_statement",
            "break_statement",
            "continue_statement",
            "goto_statement",
            "case_statement",
            "try_statement",
            "throw_statement",
        }

    def _get_scope_name(self, node: Node) -> str | None:
        """Extract namespace, class, or function scope names from ancestor nodes."""
        if node.type in ("namespace_definition", "class_specifier", "struct_specifier"):
            for child in node.children:
                if child.type in ("name", "type_identifier", "namespace_identifier"):
                    return child.text.decode("utf-8") if child.text else None
        # Recognise function_definition as a scope (for local classes)
        if node.type == "function_definition":
            for child in node.children:
                if child.type == "function_declarator":
                    for gc in child.children:
                        if gc.type in ("identifier", "field_identifier"):
                            return gc.text.decode("utf-8") if gc.text else None
        return None

    def _compute_fqdn(self, node: Node, name: str) -> str:
        """Compute FQDN using C++ namespace/class scoping with ``::`` separator."""
        parts: list[str] = []
        current = node.parent
        while current is not None:
            part = self._get_scope_name(current)
            if part:
                parts.append(part)
            current = current.parent
        parts.reverse()
        parts.append(name)
        return "::".join(parts) if len(parts) > 1 else name
