"""Base classes and data structures for AST analysis.

This module provides the foundation for multi-language AST parsing
and code analysis in IEUM.
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from enum import Enum
from pathlib import Path
from typing import Optional, Any


class SymbolType(Enum):
    """Type of code symbol."""

    # Common types
    FUNCTION = "function"
    METHOD = "method"
    CLASS = "class"
    VARIABLE = "variable"
    CONSTANT = "constant"
    PARAMETER = "parameter"
    PROPERTY = "property"

    # Python specific
    MODULE = "module"
    DECORATOR = "decorator"
    ASYNC_FUNCTION = "async_function"

    # C/C++ specific
    STRUCT = "struct"
    UNION = "union"
    ENUM = "enum"
    ENUM_MEMBER = "enum_member"
    TYPEDEF = "typedef"
    MACRO = "macro"
    NAMESPACE = "namespace"
    TEMPLATE = "template"

    # MFC specific
    MESSAGE_HANDLER = "message_handler"
    MESSAGE_MAP = "message_map"
    DIALOG_CLASS = "dialog_class"
    VIEW_CLASS = "view_class"
    DOCUMENT_CLASS = "document_class"

    # General
    IMPORT = "import"
    EXPORT = "export"
    UNKNOWN = "unknown"


class Visibility(Enum):
    """Access visibility for class members."""

    PUBLIC = "public"
    PROTECTED = "protected"
    PRIVATE = "private"
    INTERNAL = "internal"  # For some languages
    DEFAULT = "default"


@dataclass
class CodeRange:
    """Represents a range in source code."""

    start_line: int
    start_column: int
    end_line: int
    end_column: int

    def contains(self, line: int, column: int) -> bool:
        """Check if a position is within this range."""
        if line < self.start_line or line > self.end_line:
            return False
        if line == self.start_line and column < self.start_column:
            return False
        if line == self.end_line and column > self.end_column:
            return False
        return True

    def __str__(self) -> str:
        return f"{self.start_line}:{self.start_column}-{self.end_line}:{self.end_column}"


@dataclass
class Symbol:
    """Represents a code symbol (function, class, variable, etc.)."""

    name: str
    type: SymbolType
    file_path: str
    range: CodeRange

    # Optional metadata
    parent: Optional[str] = None  # Parent symbol name (e.g., class for method)
    signature: Optional[str] = None  # Full signature for functions
    docstring: Optional[str] = None
    visibility: Visibility = Visibility.DEFAULT
    is_static: bool = False
    is_async: bool = False
    return_type: Optional[str] = None
    parameters: list[dict] = field(default_factory=list)
    decorators: list[str] = field(default_factory=list)
    base_classes: list[str] = field(default_factory=list)
    metadata: dict[str, Any] = field(default_factory=dict)

    @property
    def line(self) -> int:
        """Start line number (1-based)."""
        return self.range.start_line

    @property
    def column(self) -> int:
        """Start column number (0-based)."""
        return self.range.start_column

    @property
    def qualified_name(self) -> str:
        """Full qualified name including parent."""
        if self.parent:
            return f"{self.parent}.{self.name}"
        return self.name

    def to_dict(self) -> dict:
        """Convert to dictionary for serialization."""
        return {
            "name": self.name,
            "type": self.type.value,
            "file_path": self.file_path,
            "line": self.line,
            "column": self.column,
            "end_line": self.range.end_line,
            "end_column": self.range.end_column,
            "parent": self.parent,
            "signature": self.signature,
            "docstring": self.docstring,
            "visibility": self.visibility.value,
            "is_static": self.is_static,
            "is_async": self.is_async,
            "return_type": self.return_type,
            "parameters": self.parameters,
            "decorators": self.decorators,
            "base_classes": self.base_classes,
        }


@dataclass
class Reference:
    """Represents a reference to a symbol."""

    symbol_name: str
    file_path: str
    line: int
    column: int
    context: str  # Surrounding code context
    reference_type: str = "usage"  # usage, call, import, inherit, etc.

    def to_dict(self) -> dict:
        """Convert to dictionary for serialization."""
        return {
            "symbol_name": self.symbol_name,
            "file_path": self.file_path,
            "line": self.line,
            "column": self.column,
            "context": self.context,
            "reference_type": self.reference_type,
        }


@dataclass
class ParseResult:
    """Result of parsing a file."""

    file_path: str
    symbols: list[Symbol] = field(default_factory=list)
    references: list[Reference] = field(default_factory=list)
    imports: list[dict] = field(default_factory=list)
    errors: list[str] = field(default_factory=list)
    language: str = "unknown"

    @property
    def success(self) -> bool:
        """Check if parsing was successful."""
        return len(self.errors) == 0

    def get_symbols_by_type(self, symbol_type: SymbolType) -> list[Symbol]:
        """Get all symbols of a specific type."""
        return [s for s in self.symbols if s.type == symbol_type]

    def get_symbol_at(self, line: int, column: int) -> Optional[Symbol]:
        """Get symbol at a specific position."""
        for symbol in self.symbols:
            if symbol.range.contains(line, column):
                return symbol
        return None

    def to_dict(self) -> dict:
        """Convert to dictionary for serialization."""
        return {
            "file_path": self.file_path,
            "language": self.language,
            "symbols": [s.to_dict() for s in self.symbols],
            "references": [r.to_dict() for r in self.references],
            "imports": self.imports,
            "errors": self.errors,
        }


class BaseASTParser(ABC):
    """Abstract base class for AST parsers."""

    # File extensions this parser handles
    EXTENSIONS: list[str] = []

    # Language name
    LANGUAGE: str = "unknown"

    def can_parse(self, file_path: str | Path) -> bool:
        """Check if this parser can handle the given file.

        Args:
            file_path: Path to the file.

        Returns:
            True if this parser can parse the file.
        """
        suffix = Path(file_path).suffix.lower()
        return suffix in self.EXTENSIONS

    @abstractmethod
    def parse_file(self, file_path: str | Path) -> ParseResult:
        """Parse a file and extract symbols.

        Args:
            file_path: Path to the file to parse.

        Returns:
            ParseResult with extracted symbols and metadata.
        """
        pass

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

        Args:
            source: Source code to parse.
            file_path: Virtual file path for the source.

        Returns:
            ParseResult with extracted symbols.
        """
        pass

    def find_symbol(
        self,
        result: ParseResult,
        name: str,
        symbol_type: Optional[SymbolType] = None,
    ) -> Optional[Symbol]:
        """Find a symbol by name in parse results.

        Args:
            result: Parse result to search.
            name: Symbol name to find.
            symbol_type: Optional type filter.

        Returns:
            The found symbol or None.
        """
        for symbol in result.symbols:
            if symbol.name == name:
                if symbol_type is None or symbol.type == symbol_type:
                    return symbol
        return None

    def get_function_at_line(
        self,
        result: ParseResult,
        line: int,
    ) -> Optional[Symbol]:
        """Get the function containing a specific line.

        Args:
            result: Parse result to search.
            line: Line number (1-based).

        Returns:
            The containing function symbol or None.
        """
        for symbol in result.symbols:
            if symbol.type in (SymbolType.FUNCTION, SymbolType.METHOD, SymbolType.ASYNC_FUNCTION):
                if symbol.range.start_line <= line <= symbol.range.end_line:
                    return symbol
        return None


def get_parser_for_file(file_path: str | Path) -> Optional[BaseASTParser]:
    """Get the appropriate parser for a file.

    Args:
        file_path: Path to the file.

    Returns:
        An appropriate parser instance or None.
    """
    from ieum.ast.python_parser import PythonASTParser
    from ieum.ast.cpp_parser import CppASTParser
    from ieum.ast.mfc_parser import MFCParser

    suffix = Path(file_path).suffix.lower()

    # Python
    if suffix in [".py", ".pyi", ".pyw"]:
        return PythonASTParser()

    # C++
    if suffix in [".cpp", ".cxx", ".cc", ".c", ".h", ".hpp", ".hxx"]:
        # Check for MFC patterns
        try:
            with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
                content = f.read(2000)  # Read first 2KB
                if any(pattern in content for pattern in [
                    "#include <afxwin.h>",
                    "#include <afx.h>",
                    "CWinApp",
                    "CDialog",
                    "BEGIN_MESSAGE_MAP",
                ]):
                    return MFCParser()
        except Exception:
            pass
        return CppASTParser()

    return None
