"""C++ AST parser using tree-sitter or regex fallback.

This module provides C++ code analysis for symbol extraction
and code understanding. Uses tree-sitter when available,
falls back to regex-based parsing otherwise.
"""

from __future__ import annotations

import re
from pathlib import Path
from typing import Optional, Any

from ieum.ast.base import (
    BaseASTParser,
    Symbol,
    SymbolType,
    Reference,
    CodeRange,
    ParseResult,
    Visibility,
)


# Try to import tree-sitter
_HAS_TREE_SITTER = False
_ts_language = None
_ts_parser = None

try:
    from tree_sitter import Language, Parser
    import tree_sitter_cpp

    _ts_language = Language(tree_sitter_cpp.language())
    _ts_parser = Parser(_ts_language)
    _HAS_TREE_SITTER = True
except ImportError:
    pass


class CppASTParser(BaseASTParser):
    """C++ AST parser using tree-sitter or regex fallback."""

    EXTENSIONS = [".cpp", ".cxx", ".cc", ".c", ".h", ".hpp", ".hxx", ".hh"]
    LANGUAGE = "cpp"

    def __init__(self):
        """Initialize the C++ parser."""
        self.use_tree_sitter = _HAS_TREE_SITTER

    def parse_file(self, file_path: str | Path) -> ParseResult:
        """Parse a C++ file.

        Args:
            file_path: Path to the C++ file.

        Returns:
            ParseResult with extracted symbols.
        """
        file_path = Path(file_path)
        result = ParseResult(file_path=str(file_path), language=self.LANGUAGE)

        try:
            with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
                source = f.read()
            return self.parse_source(source, str(file_path))
        except FileNotFoundError:
            result.errors.append(f"File not found: {file_path}")
        except PermissionError:
            result.errors.append(f"Permission denied: {file_path}")
        except Exception as e:
            result.errors.append(f"Error reading file: {e}")

        return result

    def parse_source(self, source: str, file_path: str = "<string>") -> ParseResult:
        """Parse C++ source code.

        Args:
            source: C++ source code.
            file_path: Virtual file path.

        Returns:
            ParseResult with extracted symbols.
        """
        result = ParseResult(file_path=file_path, language=self.LANGUAGE)

        if self.use_tree_sitter:
            self._parse_with_tree_sitter(source, result)
        else:
            self._parse_with_regex(source, result)

        self._extract_includes(source, result)
        return result

    def parse_string(self, source: str, file_path: str = "<string>") -> ParseResult:
        """Alias for parse_source for convenience."""
        return self.parse_source(source, file_path)

    def _parse_with_tree_sitter(self, source: str, result: ParseResult) -> None:
        """Parse using tree-sitter.

        Args:
            source: Source code.
            result: ParseResult to populate.
        """
        if not _ts_parser:
            return

        try:
            tree = _ts_parser.parse(source.encode("utf-8"))
            self._walk_tree_sitter(tree.root_node, result, source)
        except Exception as e:
            result.errors.append(f"Tree-sitter parse error: {e}")
            # Fall back to regex
            self._parse_with_regex(source, result)

    def _walk_tree_sitter(
        self,
        node: Any,
        result: ParseResult,
        source: str,
        parent: Optional[str] = None,
        visibility: Visibility = Visibility.DEFAULT,
    ) -> None:
        """Walk tree-sitter AST and extract symbols.

        Args:
            node: Tree-sitter node.
            result: ParseResult to populate.
            source: Original source code.
            parent: Parent symbol name.
            visibility: Current visibility scope.
        """
        node_type = node.type

        # Track visibility in class body
        if node_type == "access_specifier":
            text = node.text.decode("utf-8").strip().rstrip(":")
            if text == "public":
                visibility = Visibility.PUBLIC
            elif text == "protected":
                visibility = Visibility.PROTECTED
            elif text == "private":
                visibility = Visibility.PRIVATE

        # Function definitions
        elif node_type == "function_definition":
            symbol = self._create_function_from_ts(node, result.file_path, parent, visibility)
            if symbol:
                result.symbols.append(symbol)

        # Class/struct definitions
        elif node_type in ("class_specifier", "struct_specifier"):
            symbol = self._create_class_from_ts(node, result.file_path, parent, node_type)
            if symbol:
                result.symbols.append(symbol)
                # Process class body
                body = node.child_by_field_name("body")
                if body:
                    for child in body.children:
                        self._walk_tree_sitter(
                            child, result, source, symbol.name,
                            Visibility.PRIVATE if node_type == "class_specifier" else Visibility.PUBLIC
                        )
                return  # Don't recurse again

        # Enum definitions
        elif node_type == "enum_specifier":
            symbol = self._create_enum_from_ts(node, result.file_path, parent)
            if symbol:
                result.symbols.append(symbol)

        # Namespace
        elif node_type == "namespace_definition":
            name_node = node.child_by_field_name("name")
            if name_node:
                name = name_node.text.decode("utf-8")
                symbol = Symbol(
                    name=name,
                    type=SymbolType.NAMESPACE,
                    file_path=result.file_path,
                    range=CodeRange(
                        start_line=node.start_point[0] + 1,
                        start_column=node.start_point[1],
                        end_line=node.end_point[0] + 1,
                        end_column=node.end_point[1],
                    ),
                )
                result.symbols.append(symbol)

        # Recurse into children
        for child in node.children:
            self._walk_tree_sitter(child, result, source, parent, visibility)

    def _create_function_from_ts(
        self,
        node: Any,
        file_path: str,
        parent: Optional[str],
        visibility: Visibility,
    ) -> Optional[Symbol]:
        """Create function symbol from tree-sitter node."""
        declarator = node.child_by_field_name("declarator")
        if not declarator:
            return None

        # Get function name
        name = None
        if declarator.type == "function_declarator":
            name_node = declarator.child_by_field_name("declarator")
            if name_node:
                name = name_node.text.decode("utf-8")
        elif declarator.type == "pointer_declarator":
            # Handle pointer return types
            inner = declarator.child_by_field_name("declarator")
            if inner and inner.type == "function_declarator":
                name_node = inner.child_by_field_name("declarator")
                if name_node:
                    name = name_node.text.decode("utf-8")

        if not name:
            return None

        # Get return type
        return_type = None
        type_node = node.child_by_field_name("type")
        if type_node:
            return_type = type_node.text.decode("utf-8")

        # Check for static
        is_static = False
        for child in node.children:
            if child.type == "storage_class_specifier":
                if child.text.decode("utf-8") == "static":
                    is_static = True

        # Build signature
        signature = node.text.decode("utf-8").split("{")[0].strip()
        if len(signature) > 100:
            signature = signature[:100] + "..."

        return Symbol(
            name=name,
            type=SymbolType.METHOD if parent else SymbolType.FUNCTION,
            file_path=file_path,
            range=CodeRange(
                start_line=node.start_point[0] + 1,
                start_column=node.start_point[1],
                end_line=node.end_point[0] + 1,
                end_column=node.end_point[1],
            ),
            parent=parent,
            signature=signature,
            return_type=return_type,
            visibility=visibility,
            is_static=is_static,
        )

    def _create_class_from_ts(
        self,
        node: Any,
        file_path: str,
        parent: Optional[str],
        node_type: str,
    ) -> Optional[Symbol]:
        """Create class/struct symbol from tree-sitter node."""
        name_node = node.child_by_field_name("name")
        if not name_node:
            return None

        name = name_node.text.decode("utf-8")

        # Get base classes
        base_classes = []
        base_clause = None
        for child in node.children:
            if child.type == "base_class_clause":
                base_clause = child
                break

        if base_clause:
            for child in base_clause.children:
                if child.type == "type_identifier":
                    base_classes.append(child.text.decode("utf-8"))

        return Symbol(
            name=name,
            type=SymbolType.CLASS if node_type == "class_specifier" else SymbolType.STRUCT,
            file_path=file_path,
            range=CodeRange(
                start_line=node.start_point[0] + 1,
                start_column=node.start_point[1],
                end_line=node.end_point[0] + 1,
                end_column=node.end_point[1],
            ),
            parent=parent,
            base_classes=base_classes,
        )

    def _create_enum_from_ts(
        self,
        node: Any,
        file_path: str,
        parent: Optional[str],
    ) -> Optional[Symbol]:
        """Create enum symbol from tree-sitter node."""
        name_node = node.child_by_field_name("name")
        if not name_node:
            return None

        return Symbol(
            name=name_node.text.decode("utf-8"),
            type=SymbolType.ENUM,
            file_path=file_path,
            range=CodeRange(
                start_line=node.start_point[0] + 1,
                start_column=node.start_point[1],
                end_line=node.end_point[0] + 1,
                end_column=node.end_point[1],
            ),
            parent=parent,
        )

    def _parse_with_regex(self, source: str, result: ParseResult) -> None:
        """Parse using regex patterns (fallback when tree-sitter unavailable).

        Args:
            source: Source code.
            result: ParseResult to populate.
        """
        lines = source.split("\n")

        # Function pattern
        func_pattern = re.compile(
            r"^\s*(?:(?:static|virtual|inline|explicit|constexpr)\s+)*"
            r"([\w:*&<>,\s]+?)\s+"  # Return type
            r"([\w:~]+)\s*"  # Function name
            r"\(([^)]*)\)\s*"  # Parameters
            r"(?:const\s*)?(?:override\s*)?(?:final\s*)?"
            r"(?:=\s*(?:default|delete)\s*)?[{;]",
            re.MULTILINE
        )

        # Class pattern
        class_pattern = re.compile(
            r"^\s*(class|struct)\s+([\w]+)"
            r"(?:\s*:\s*((?:public|protected|private)\s+[\w:]+(?:\s*,\s*(?:public|protected|private)\s+[\w:]+)*))?"
            r"\s*\{",
            re.MULTILINE
        )

        # Enum pattern
        enum_pattern = re.compile(
            r"^\s*enum\s+(?:class\s+)?([\w]+)",
            re.MULTILINE
        )

        # Find classes
        for match in class_pattern.finditer(source):
            class_type = match.group(1)
            name = match.group(2)
            bases_str = match.group(3) or ""

            # Parse base classes
            base_classes = []
            if bases_str:
                for base in bases_str.split(","):
                    base = re.sub(r"public|protected|private", "", base).strip()
                    if base:
                        base_classes.append(base)

            start_line = source[:match.start()].count("\n") + 1
            end_line = self._find_block_end(source, match.end())

            result.symbols.append(Symbol(
                name=name,
                type=SymbolType.CLASS if class_type == "class" else SymbolType.STRUCT,
                file_path=result.file_path,
                range=CodeRange(
                    start_line=start_line,
                    start_column=0,
                    end_line=end_line,
                    end_column=0,
                ),
                base_classes=base_classes,
            ))

        # Find functions
        for match in func_pattern.finditer(source):
            return_type = match.group(1).strip()
            name = match.group(2)
            params = match.group(3)

            # Skip constructors/destructors detected as regular functions
            if return_type in ("if", "while", "for", "switch", "catch"):
                continue

            start_line = source[:match.start()].count("\n") + 1

            # Try to find end of function
            if source[match.end() - 1] == "{":
                end_line = self._find_block_end(source, match.end())
            else:
                end_line = start_line

            result.symbols.append(Symbol(
                name=name,
                type=SymbolType.FUNCTION,
                file_path=result.file_path,
                range=CodeRange(
                    start_line=start_line,
                    start_column=0,
                    end_line=end_line,
                    end_column=0,
                ),
                return_type=return_type,
                signature=f"{return_type} {name}({params})",
            ))

        # Find enums
        for match in enum_pattern.finditer(source):
            name = match.group(1)
            start_line = source[:match.start()].count("\n") + 1

            result.symbols.append(Symbol(
                name=name,
                type=SymbolType.ENUM,
                file_path=result.file_path,
                range=CodeRange(
                    start_line=start_line,
                    start_column=0,
                    end_line=start_line,
                    end_column=0,
                ),
            ))

    def _find_block_end(self, source: str, start: int) -> int:
        """Find the line number where a block ends.

        Args:
            source: Source code.
            start: Starting position after '{'.

        Returns:
            End line number.
        """
        depth = 1
        pos = start

        while pos < len(source) and depth > 0:
            char = source[pos]
            if char == "{":
                depth += 1
            elif char == "}":
                depth -= 1
            pos += 1

        return source[:pos].count("\n") + 1

    def _extract_includes(self, source: str, result: ParseResult) -> None:
        """Extract #include statements.

        Args:
            source: Source code.
            result: ParseResult to populate.
        """
        include_pattern = re.compile(r'#include\s*([<"])([^>"]+)[>"]')

        for match in include_pattern.finditer(source):
            bracket = match.group(1)
            path = match.group(2)
            line = source[:match.start()].count("\n") + 1

            result.imports.append({
                "type": "system" if bracket == "<" else "local",
                "path": path,
                "line": line,
            })

    def get_all_functions(self, source: str) -> list[Symbol]:
        """Get all functions from source code.

        Args:
            source: C++ source code.

        Returns:
            List of function symbols.
        """
        result = self.parse_source(source)
        return [
            s for s in result.symbols
            if s.type in (SymbolType.FUNCTION, SymbolType.METHOD)
        ]

    def get_all_classes(self, source: str) -> list[Symbol]:
        """Get all classes and structs from source code.

        Args:
            source: C++ source code.

        Returns:
            List of class/struct symbols.
        """
        result = self.parse_source(source)
        return [
            s for s in result.symbols
            if s.type in (SymbolType.CLASS, SymbolType.STRUCT)
        ]
