# SPDX-License-Identifier: Apache-2.0

"""
Whoosh-Based Intelligent Code Search System
Provides robust, language-agnostic code search capabilities using Whoosh full-text search
instead of hardcoded language patterns.
"""

import os
import json
import logging
import re
import hashlib
from pathlib import Path
from typing import Dict, List, Any, Optional, Tuple
from dataclasses import dataclass

from whoosh import index
from whoosh.fields import Schema, TEXT, ID, KEYWORD, NUMERIC
from whoosh.qparser import QueryParser, MultifieldParser
from whoosh.query import And, Or, Term, Wildcard, FuzzyTerm
from whoosh import highlight
from whoosh.analysis import StemmingAnalyzer, StandardAnalyzer

logger = logging.getLogger(__name__)

@dataclass 
class SearchResult:
    """Represents a code search result."""
    file_path: str
    line_number: int
    line_content: str
    context_before: List[str]
    context_after: List[str]
    match_type: str  # 'function', 'class', 'import', 'variable', 'pattern'
    score: float  # Whoosh relevance score
    language: str
    highlighted_content: str = ""
    
class LanguageDetector:
    """Detects programming language from file extensions and content."""
    
    # Comprehensive language mapping
    EXTENSION_MAP = {
        # Web languages
        '.py': 'python', '.pyx': 'python', '.pyi': 'python',
        '.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript',
        '.ts': 'typescript', '.tsx': 'typescript',
        '.html': 'html', '.htm': 'html',
        '.css': 'css', '.scss': 'sass', '.sass': 'sass', '.less': 'less',
        '.php': 'php',
        
        # Systems languages
        '.c': 'c', '.h': 'c',
        '.cpp': 'cpp', '.cxx': 'cpp', '.cc': 'cpp', '.hpp': 'cpp',
        '.cs': 'csharp',
        '.java': 'java',
        '.go': 'go',
        '.rs': 'rust',
        '.swift': 'swift',
        '.m': 'objective-c', '.mm': 'objective-c',
        
        # Functional languages
        '.hs': 'haskell', '.lhs': 'haskell',
        '.ml': 'ocaml', '.mli': 'ocaml',
        '.fs': 'fsharp', '.fsi': 'fsharp',
        '.scala': 'scala',
        '.clj': 'clojure', '.cljs': 'clojure',
        '.elm': 'elm',
        
        # Ruby and Python-like
        '.rb': 'ruby', '.rbw': 'ruby',
        '.lua': 'lua',
        '.pl': 'perl', '.pm': 'perl',
        '.r': 'r', '.R': 'r',
        '.jl': 'julia',
        
        # Shell and scripting
        '.sh': 'bash', '.bash': 'bash',
        '.zsh': 'zsh', '.fish': 'fish',
        '.ps1': 'powershell',
        '.bat': 'batch', '.cmd': 'batch',
        
        # Markup and config
        '.xml': 'xml', '.xhtml': 'xml',
        '.json': 'json', '.jsonl': 'json',
        '.yaml': 'yaml', '.yml': 'yaml',
        '.toml': 'toml',
        '.ini': 'ini', '.cfg': 'ini',
        '.sql': 'sql',
        
        # Mobile
        '.kt': 'kotlin', '.kts': 'kotlin',
        '.dart': 'dart',
        
        # Assembly and low-level
        '.asm': 'assembly', '.s': 'assembly',
        
        # Other
        '.vim': 'vim', '.vimrc': 'vim',
        '.md': 'markdown', '.markdown': 'markdown',
        '.tex': 'latex',
        '.dockerfile': 'dockerfile',
        '.makefile': 'makefile',
    }
    
    def detect_language(self, file_path: Path) -> str:
        """Detect programming language from file path and content."""
        # First try extension
        extension = file_path.suffix.lower()
        if extension in self.EXTENSION_MAP:
            return self.EXTENSION_MAP[extension]
            
        # Check filename patterns
        filename_lower = file_path.name.lower()
        if filename_lower in ['makefile', 'dockerfile', 'rakefile', 'gemfile']:
            return filename_lower
        if filename_lower.endswith('file'):
            return 'makefile'
            
        # If no extension, try content-based detection
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                first_lines = f.read(1000)  # Read first 1KB
                return self._detect_from_content(first_lines)
        except Exception:
            return 'text'
    
    def _detect_from_content(self, content: str) -> str:
        """Detect language from file content."""
        content_lower = content.lower()
        
        # Shebang detection
        if content.startswith('#!'):
            shebang = content.split('\n')[0]
            if 'python' in shebang: return 'python'
            if 'node' in shebang or 'javascript' in shebang: return 'javascript'
            if 'ruby' in shebang: return 'ruby'
            if 'bash' in shebang or 'sh' in shebang: return 'bash'
            if 'perl' in shebang: return 'perl'
            
        # Language-specific patterns
        patterns = {
            'python': ['def ', 'import ', 'from ', 'class ', '__init__'],
            'javascript': ['function ', 'var ', 'let ', 'const ', '=> '],
            'java': ['public class', 'private ', 'public ', 'import java'],
            'c': ['#include', 'int main', 'printf', 'malloc'],
            'cpp': ['#include', 'std::', 'namespace', 'cout'],
            'go': ['func ', 'package ', 'import ', 'var ', 'type '],
            'rust': ['fn ', 'let ', 'match ', 'impl ', 'struct '],
            'ruby': ['def ', 'class ', 'require ', 'end\n', 'puts '],
        }
        
        scores = {}
        for lang, keywords in patterns.items():
            scores[lang] = sum(1 for kw in keywords if kw in content_lower)
            
        if scores:
            return max(scores.items(), key=lambda x: x[1])[0]
            
        return 'text'

class WhooshCodeSearch:
    """
    Whoosh-based intelligent code search system that provides robust,
    language-agnostic search capabilities using full-text indexing.
    """
    
    def __init__(self, repo_path: str, auto_index: bool = False):
        """Initialize the Whoosh code search system.
        
        Args:
            repo_path: Path to the repository to search
            auto_index: If True, automatically build index on initialization
        """
        self.repo_path = Path(repo_path)
        self.language_detector = LanguageDetector()
        self.index_dir = self.repo_path / ".swe_search_index"
        self.index_dir.mkdir(exist_ok=True)
        
        # Define Whoosh schema for code files
        self.schema = Schema(
            file_path=ID(stored=True, unique=True),
            content=TEXT(stored=True, analyzer=StandardAnalyzer()),
            line_content=TEXT(stored=True, analyzer=StandardAnalyzer()),
            line_number=NUMERIC(stored=True),
            language=KEYWORD(stored=True),
            function_names=TEXT(stored=True, analyzer=StemmingAnalyzer()),
            class_names=TEXT(stored=True, analyzer=StemmingAnalyzer()),
            imports=TEXT(stored=True, analyzer=StandardAnalyzer()),
            variables=TEXT(stored=True, analyzer=StemmingAnalyzer()),
            comments=TEXT(stored=True, analyzer=StandardAnalyzer()),
            file_hash=ID(stored=True)  # For tracking file changes
        )
        
        self.index = None
        self._initialize_index(auto_index)
    
    def _initialize_index(self, auto_index: bool = False):
        """Initialize or open the Whoosh index.
        
        Args:
            auto_index: If True, automatically rebuild index if needed
        """
        try:
            if index.exists_in(str(self.index_dir)):
                self.index = index.open_dir(str(self.index_dir))
                logger.info("📚 Opened existing search index")
                
                # Only auto-rebuild if explicitly requested and index is outdated
                if auto_index and self._needs_reindexing():
                    logger.info("🔄 Auto-indexing enabled, rebuilding index...")
                    self.rebuild_index()
                    
            else:
                self.index = index.create_in(str(self.index_dir), self.schema)
                logger.info("🆕 Created new search index")
                
                # Build initial index if auto_index is enabled
                if auto_index:
                    logger.info("🔄 Building initial index...")
                    self.rebuild_index()
                else:
                    logger.info("💡 Index will be built on first search")
                
        except Exception as e:
            logger.error(f"Error initializing search index: {e}")
            # Fallback: create new index
            self.index = index.create_in(str(self.index_dir), self.schema)
            self._create_minimal_index()
    
    def _needs_reindexing(self) -> bool:
        """Check if the index needs to be rebuilt based on file modifications."""
        if not self.index:
            return True
            
        with self.index.searcher() as searcher:
            indexed_files = {doc['file_path'] for doc in searcher.documents()}
            
        current_files = set()
        for file_path in self._get_files_to_search():
            rel_path = str(file_path.relative_to(self.repo_path))
            current_files.add(rel_path)
            
        # Simple check: if file counts differ significantly, reindex
        if len(current_files) != len(indexed_files):
            return True
            
        # Check for new files
        if current_files - indexed_files:
            return True
            
        return False
    
    def rebuild_index(self):
        """Rebuild the entire search index."""
        logger.info("🔄 Rebuilding search index...")
        
        try:
            files_to_index = self._get_files_to_search()
            
            # Limit files for large repositories to avoid timeout
            if len(files_to_index) > 1000:
                logger.info(f"Large repository detected ({len(files_to_index)} files), limiting to first 1000 files")
                files_to_index = files_to_index[:1000]
            
            writer = self.index.writer()
            try:
                # Clear existing index
                writer.delete_by_query(Term("file_path", "*"))
                
                for i, file_path in enumerate(files_to_index):
                    try:
                        self._index_file(writer, file_path)
                        if i % 50 == 0:  # More frequent progress updates
                            logger.info(f"📝 Indexed {i}/{len(files_to_index)} files")
                    except Exception as e:
                        logger.warning(f"Error indexing {file_path}: {e}")
                        continue
                        
                writer.commit()
                logger.info(f"✅ Index rebuilt with {len(files_to_index)} files")
                
            except Exception as e:
                logger.error(f"Error during indexing: {e}")
                writer.cancel()  # Cancel the transaction
                raise
            finally:
                if hasattr(writer, 'is_closed') and not writer.is_closed():
                    writer.cancel()  # Ensure writer is properly closed
                    
        except Exception as e:
            logger.error(f"Failed to rebuild index: {e}")
            # Create a minimal working index
            self._create_minimal_index()
    
    def _index_file(self, writer, file_path: Path):
        """Index a single file."""
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                content = f.read()
                lines = content.splitlines()
        except Exception as e:
            logger.warning(f"Cannot read {file_path}: {e}")
            return
            
        language = self.language_detector.detect_language(file_path)
        rel_path = str(file_path.relative_to(self.repo_path))
        
        # Calculate file hash for change detection
        file_hash = hashlib.md5(content.encode()).hexdigest()
        
        # Extract code elements
        functions = self._extract_functions(content, language)
        classes = self._extract_classes(content, language)
        imports = self._extract_imports(content, language)
        variables = self._extract_variables(content, language)
        comments = self._extract_comments(content, language)
        
        # Index each line separately for precise line-level search
        for line_num, line in enumerate(lines, 1):
            if line.strip():  # Skip empty lines
                writer.add_document(
                    file_path=rel_path,
                    content=content,
                    line_content=line,
                    line_number=line_num,
                    language=language,
                    function_names=" ".join(functions),
                    class_names=" ".join(classes),
                    imports=" ".join(imports),
                    variables=" ".join(variables),
                    comments=" ".join(comments),
                    file_hash=file_hash
                )
    
    def search_code_intelligent(self, 
                               query: str, 
                               file_patterns: Optional[List[str]] = None,
                               search_type: str = "semantic",
                               max_results: int = 50) -> List[SearchResult]:
        """
        Perform intelligent code search using Whoosh full-text search.
        
        Args:
            query: Search query (natural language or code pattern)
            file_patterns: Optional file patterns to search (e.g., ['*.py', '*.js'])
            search_type: 'semantic', 'exact', 'fuzzy', or 'function'
            max_results: Maximum number of results to return
            
        Returns:
            List of SearchResult objects
        """
        if not self.index:
            logger.error("Search index not available")
            return []
        
        # Check if index is empty and build it if needed
        if self._is_index_empty():
            logger.info("📝 Index is empty, building index for search...")
            self.rebuild_index()
            
        logger.info(f"🔍 Whoosh search for: '{query}' (type: {search_type})")
        
        # Build Whoosh query based on search type
        whoosh_query = self._build_whoosh_query(query, search_type, file_patterns)
        
        results = []
        with self.index.searcher() as searcher:
            # Create highlighter for showing matched terms
            highlighter = highlight.Highlighter(
                formatter=highlight.HtmlFormatter(tagname="mark"),
                fragmenter=highlight.WholeFragmenter()
            )
            
            search_results = searcher.search(whoosh_query, limit=max_results * 2)  # Get more to filter
            
            for hit in search_results:
                try:
                    # Get context lines
                    context_before, context_after = self._get_context_lines(
                        Path(self.repo_path / hit['file_path']), 
                        int(hit['line_number'])
                    )
                    
                    # Determine match type
                    match_type = self._determine_match_type(hit, query)
                    
                    # Get highlighted content
                    highlighted = highlighter.highlight_hit(hit, "line_content") or hit['line_content']
                    
                    result = SearchResult(
                        file_path=hit['file_path'],
                        line_number=int(hit['line_number']),
                        line_content=hit['line_content'],
                        context_before=context_before,
                        context_after=context_after,
                        match_type=match_type,
                        score=hit.score,
                        language=hit['language'],
                        highlighted_content=highlighted
                    )
                    results.append(result)
                    
                except Exception as e:
                    logger.warning(f"Error processing search hit: {e}")
                    continue
        
        # Sort by score and limit results
        results.sort(key=lambda x: x.score, reverse=True)
        return results[:max_results]
    
    def _build_whoosh_query(self, query: str, search_type: str, file_patterns: Optional[List[str]]):
        """Build Whoosh query based on search parameters."""
        if search_type == "exact":
            # Exact phrase matching
            parser = QueryParser("line_content", self.index.schema)
            return parser.parse(f'"{query}"')
            
        elif search_type == "fuzzy":
            # Fuzzy matching
            parser = QueryParser("line_content", self.index.schema)
            terms = query.split()
            fuzzy_terms = [f"{term}~" for term in terms]  # ~ enables fuzzy matching
            return parser.parse(" ".join(fuzzy_terms))
            
        elif search_type == "function":
            # Search specifically in function names
            parser = MultifieldParser(["function_names", "line_content"], self.index.schema)
            return parser.parse(query)
            
        elif search_type == "class":
            # Search specifically in class names  
            parser = MultifieldParser(["class_names", "line_content"], self.index.schema)
            return parser.parse(query)
            
        elif search_type == "import":
            # Search specifically in imports
            parser = MultifieldParser(["imports", "line_content"], self.index.schema)
            return parser.parse(query)
            
        else:  # semantic (default)
            # Multi-field semantic search
            parser = MultifieldParser([
                "line_content", "function_names", "class_names", 
                "imports", "variables", "comments"
            ], self.index.schema)
            return parser.parse(query)
    
    def find_functions_intelligent(self, 
                                 function_name: str,
                                 file_patterns: Optional[List[str]] = None,
                                 max_results: int = 20) -> List[SearchResult]:
        """Find function definitions using Whoosh search."""
        logger.info(f"🎯 Finding functions: '{function_name}'")
        return self.search_code_intelligent(function_name, file_patterns, "function", max_results=max_results)
    
    def find_classes_intelligent(self, 
                               class_name: str,
                               file_patterns: Optional[List[str]] = None,
                               max_results: int = 20) -> List[SearchResult]:
        """Find class definitions using Whoosh search."""
        logger.info(f"🏛️ Finding classes: '{class_name}'")
        return self.search_code_intelligent(class_name, file_patterns, "class", max_results=max_results)
    
    def find_imports_intelligent(self, 
                               module_name: str,
                               file_patterns: Optional[List[str]] = None) -> List[SearchResult]:
        """Find import statements using Whoosh search."""
        logger.info(f"📦 Finding imports: '{module_name}'")
        return self.search_code_intelligent(module_name, file_patterns, "import")
    
    def _get_files_to_search(self, file_patterns: Optional[List[str]] = None) -> List[Path]:
        """Get list of files to search based on patterns."""
        if not file_patterns:
            # Default: search common code file types
            file_patterns = [
                '*.py', '*.js', '*.ts', '*.jsx', '*.tsx',
                '*.java', '*.c', '*.cpp', '*.h', '*.hpp',
                '*.go', '*.rs', '*.rb', '*.php', '*.swift',
                '*.kt', '*.scala', '*.cs', '*.hs', '*.ml',
                '*.clj', '*.lua', '*.r', '*.jl', '*.dart'
            ]
        
        files = []
        for pattern in file_patterns:
            if pattern.startswith('*.'):
                extension = pattern[1:]  # Remove *
                files.extend(self.repo_path.rglob(f"*{extension}"))
            else:
                files.extend(self.repo_path.rglob(pattern))
        
        # Filter out hidden files and common build directories
        filtered_files = []
        skip_dirs = {'.git', '.svn', '.hg', 'node_modules', '__pycache__', '.pytest_cache', 'build', 'dist', '.venv', 'venv'}
        
        for file_path in files:
            if file_path.is_file() and not any(part.startswith('.') or part in skip_dirs for part in file_path.parts):
                filtered_files.append(file_path)
        
        return filtered_files
    
    def _get_context_lines(self, file_path: Path, line_number: int, context_size: int = 2) -> Tuple[List[str], List[str]]:
        """Get context lines around a specific line number."""
        try:
            with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
                lines = f.readlines()
        except Exception as e:
            logger.warning(f"Cannot read {file_path}: {e}")
            return [], []
            
        context_before = []
        context_after = []
        
        # Get lines before
        for i in range(max(0, line_number - context_size - 1), line_number - 1):
            if i < len(lines):
                context_before.append(lines[i].rstrip())
        
        # Get lines after  
        for i in range(line_number, min(len(lines), line_number + context_size)):
            if i < len(lines):
                context_after.append(lines[i].rstrip())
                
        return context_before, context_after
    
    def _determine_match_type(self, hit, query: str) -> str:
        """Determine the type of match based on the search hit."""
        line_content = hit['line_content'].lower()
        query_lower = query.lower()
        
        # Check if it's in function names field
        if query_lower in hit.get('function_names', '').lower():
            return 'function'
        elif query_lower in hit.get('class_names', '').lower():
            return 'class'  
        elif query_lower in hit.get('imports', '').lower():
            return 'import'
        elif query_lower in hit.get('variables', '').lower():
            return 'variable'
        elif query_lower in hit.get('comments', '').lower():
            return 'comment'
        else:
            return 'pattern'
    
    def _find_matches_in_line(self, line: str, query: str, search_type: str, language: str) -> List[Tuple[str, float]]:
        """Find matches in a single line with confidence scoring."""
        matches = []
        line_lower = line.lower()
        query_lower = query.lower()
        
        if search_type == "exact":
            if query in line:
                matches.append(("exact", 1.0))
        elif search_type == "fuzzy":
            # Simple fuzzy matching
            if query_lower in line_lower:
                matches.append(("fuzzy", 0.8))
            elif any(word in line_lower for word in query_lower.split()):
                matches.append(("partial", 0.6))
        else:  # semantic search
            matches.extend(self._semantic_match(line, query, language))
        
        return matches
    
    def _semantic_match(self, line: str, query: str, language: str) -> List[Tuple[str, float]]:
        """Perform semantic matching using language-aware patterns."""
        matches = []
        line_lower = line.strip().lower()
        query_lower = query.lower()
        
        # Function definition patterns
        function_patterns = {
            'python': [r'def\s+(\w+)', r'async\s+def\s+(\w+)', r'lambda\s+'],
            'javascript': [r'function\s+(\w+)', r'(\w+)\s*=\s*function', r'(\w+)\s*=>\s*', r'(\w+)\s*:\s*function'],
            'typescript': [r'function\s+(\w+)', r'(\w+)\s*=\s*function', r'(\w+)\s*=>\s*'],
            'java': [r'(public|private|protected).*?(\w+)\s*\(', r'static.*?(\w+)\s*\('],
            'cpp': [r'(\w+)\s*\([^)]*\)\s*{', r'(\w+)::\w+\s*\('],
            'c': [r'(\w+)\s*\([^)]*\)\s*{'],
            'go': [r'func\s+(\w+)', r'func\s+\([^)]*\)\s+(\w+)'],
            'rust': [r'fn\s+(\w+)', r'pub\s+fn\s+(\w+)'],
            'ruby': [r'def\s+(\w+)'],
            'php': [r'function\s+(\w+)', r'public\s+function\s+(\w+)', r'private\s+function\s+(\w+)'],
        }
        
        # Class definition patterns
        class_patterns = {
            'python': [r'class\s+(\w+)', r'class\s+(\w+)\('],
            'javascript': [r'class\s+(\w+)', r'(\w+)\s*=\s*class'],
            'typescript': [r'class\s+(\w+)', r'interface\s+(\w+)', r'type\s+(\w+)'],
            'java': [r'class\s+(\w+)', r'interface\s+(\w+)', r'enum\s+(\w+)'],
            'cpp': [r'class\s+(\w+)', r'struct\s+(\w+)'],
            'c': [r'struct\s+(\w+)', r'typedef\s+struct.*?(\w+)'],
            'go': [r'type\s+(\w+)\s+struct', r'type\s+(\w+)\s+interface'],
            'rust': [r'struct\s+(\w+)', r'enum\s+(\w+)', r'trait\s+(\w+)'],
            'ruby': [r'class\s+(\w+)', r'module\s+(\w+)'],
        }
        
        # Import patterns
        import_patterns = {
            'python': [r'import\s+(\w+)', r'from\s+(\w+)\s+import'],
            'javascript': [r'import.*?from\s+[\'"]([^\'"]+)[\'"]', r'require\s*\(\s*[\'"]([^\'"]+)[\'"]'],
            'typescript': [r'import.*?from\s+[\'"]([^\'"]+)[\'"]'],
            'java': [r'import\s+([\w.]+)'],
            'go': [r'import\s+"([^"]+)"', r'import\s+(\w+)\s+"'],
            'rust': [r'use\s+([\w:]+)'],
            'cpp': [r'#include\s*[<"]([\w./]+)[>"]'],
            'c': [r'#include\s*[<"]([\w./]+)[>"]'],
        }
        
        # Check for function definitions
        if language in function_patterns:
            for pattern in function_patterns[language]:
                matches_found = re.findall(pattern, line, re.IGNORECASE)
                for match in matches_found:
                    function_name = match if isinstance(match, str) else match[-1]
                    if query_lower in function_name.lower():
                        confidence = 1.0 if query_lower == function_name.lower() else 0.8
                        matches.append(("function", confidence))
        
        # Check for class definitions
        if language in class_patterns:
            for pattern in class_patterns[language]:
                matches_found = re.findall(pattern, line, re.IGNORECASE)
                for match in matches_found:
                    class_name = match if isinstance(match, str) else match[-1]
                    if query_lower in class_name.lower():
                        confidence = 1.0 if query_lower == class_name.lower() else 0.8
                        matches.append(("class", confidence))
        
        # Check for imports
        if language in import_patterns:
            for pattern in import_patterns[language]:
                matches_found = re.findall(pattern, line, re.IGNORECASE)
                for match in matches_found:
                    import_name = match if isinstance(match, str) else match[-1]
                    if query_lower in import_name.lower():
                        confidence = 1.0 if query_lower == import_name.lower() else 0.7
                        matches.append(("import", confidence))
        
        # Fallback: simple text matching
        if query_lower in line_lower:
            confidence = 0.6
            if line_lower.strip() == query_lower.strip():
                confidence = 1.0
            elif query_lower in line_lower.split():
                confidence = 0.8
            matches.append(("pattern", confidence))
        
            return 'comment'
        else:
            return 'pattern'
    
    def _extract_functions(self, content: str, language: str) -> List[str]:
        """Extract function names from code content."""
        functions = []
        
        # Language-specific function patterns
        patterns = {
            'python': [r'def\s+(\w+)', r'async\s+def\s+(\w+)'],
            'javascript': [r'function\s+(\w+)', r'(\w+)\s*=\s*function', r'(\w+)\s*=>\s*'],
            'typescript': [r'function\s+(\w+)', r'(\w+)\s*=\s*function', r'(\w+)\s*=>\s*'],
            'java': [r'(public|private|protected|static).*?(\w+)\s*\('],
            'cpp': [r'(\w+)\s*\([^)]*\)\s*{', r'(\w+)::\w+\s*\('],
            'c': [r'(\w+)\s*\([^)]*\)\s*{'],
            'go': [r'func\s+(\w+)', r'func\s+\([^)]*\)\s+(\w+)'],
            'rust': [r'fn\s+(\w+)', r'pub\s+fn\s+(\w+)'],
            'ruby': [r'def\s+(\w+)'],
            'php': [r'function\s+(\w+)', r'(public|private|protected)\s+function\s+(\w+)'],
        }
        
        if language in patterns:
            for pattern in patterns[language]:
                matches = re.findall(pattern, content, re.IGNORECASE | re.MULTILINE)
                for match in matches:
                    if isinstance(match, tuple):
                        # Take the last group (function name)
                        func_name = match[-1]
                    else:
                        func_name = match
                    if func_name and func_name.isidentifier():
                        functions.append(func_name)
        
        return list(set(functions))  # Remove duplicates
    
    def _extract_classes(self, content: str, language: str) -> List[str]:
        """Extract class names from code content."""
        classes = []
        
        patterns = {
            'python': [r'class\s+(\w+)'],
            'javascript': [r'class\s+(\w+)'],
            'typescript': [r'class\s+(\w+)', r'interface\s+(\w+)', r'type\s+(\w+)'],
            'java': [r'class\s+(\w+)', r'interface\s+(\w+)', r'enum\s+(\w+)'],
            'cpp': [r'class\s+(\w+)', r'struct\s+(\w+)'],
            'c': [r'struct\s+(\w+)', r'typedef\s+struct.*?(\w+)'],
            'go': [r'type\s+(\w+)\s+struct', r'type\s+(\w+)\s+interface'],
            'rust': [r'struct\s+(\w+)', r'enum\s+(\w+)', r'trait\s+(\w+)'],
            'ruby': [r'class\s+(\w+)', r'module\s+(\w+)'],
        }
        
        if language in patterns:
            for pattern in patterns[language]:
                matches = re.findall(pattern, content, re.IGNORECASE | re.MULTILINE)
                for match in matches:
                    if isinstance(match, tuple):
                        class_name = match[-1]
                    else:
                        class_name = match
                    if class_name and class_name.isidentifier():
                        classes.append(class_name)
        
        return list(set(classes))
    
    def _extract_imports(self, content: str, language: str) -> List[str]:
        """Extract import statements from code content."""
        imports = []
        
        patterns = {
            'python': [r'import\s+([\w.]+)', r'from\s+([\w.]+)\s+import'],
            'javascript': [r'import.*?from\s+[\'"]([^\'"]+)[\'"]', r'require\s*\(\s*[\'"]([^\'"]+)[\'"]'],
            'typescript': [r'import.*?from\s+[\'"]([^\'"]+)[\'"]'],
            'java': [r'import\s+([\w.]+)'],
            'go': [r'import\s+"([^"]+)"'],
            'rust': [r'use\s+([\w:]+)'],
            'cpp': [r'#include\s*[<"]([\w./]+)[>"]'],
            'c': [r'#include\s*[<"]([\w./]+)[>"]'],
        }
        
        if language in patterns:
            for pattern in patterns[language]:
                matches = re.findall(pattern, content, re.IGNORECASE | re.MULTILINE)
                imports.extend(matches)
        
        return list(set(imports))
    
    def _extract_variables(self, content: str, language: str) -> List[str]:
        """Extract variable names from code content."""
        variables = []
        
        # Simple variable extraction - can be enhanced
        if language == 'python':
            patterns = [r'(\w+)\s*=\s*[^=]', r'for\s+(\w+)\s+in', r'with.*?as\s+(\w+)']
        elif language in ['javascript', 'typescript']:
            patterns = [r'(let|const|var)\s+(\w+)', r'(\w+)\s*=\s*[^=]']
        elif language == 'java':
            patterns = [r'(int|string|boolean|double|float)\s+(\w+)', r'(\w+)\s+(\w+)\s*=']
        else:
            patterns = [r'(\w+)\s*=\s*[^=]']  # Generic pattern
            
        for pattern in patterns:
            matches = re.findall(pattern, content, re.IGNORECASE | re.MULTILINE)
            for match in matches:
                if isinstance(match, tuple):
                    var_name = match[-1]
                else:
                    var_name = match
                if var_name and var_name.isidentifier() and not var_name.startswith('_'):
                    variables.append(var_name)
        
        return list(set(variables))[:50]  # Limit to avoid too many matches
    
    def _extract_comments(self, content: str, language: str) -> List[str]:
        """Extract comments from code content."""
        comments = []
        
        if language == 'python':
            # Python single-line and docstring comments
            patterns = [r'#\s*(.*)', r'"""\s*(.*?)\s*"""', r"'''\s*(.*?)\s*'''"]
        elif language in ['javascript', 'typescript', 'java', 'cpp', 'c']:
            # C-style comments
            patterns = [r'//\s*(.*)', r'/\*\s*(.*?)\s*\*/']
        elif language == 'html':
            patterns = [r'<!--\s*(.*?)\s*-->']
        else:
            # Generic comment patterns
            patterns = [r'#\s*(.*)', r'//\s*(.*)']
            
        for pattern in patterns:
            matches = re.findall(pattern, content, re.DOTALL | re.MULTILINE)
            for match in matches:
                if match.strip():
                    comments.append(match.strip())
        
        return list(set(comments))[:20]  # Limit to avoid too many matches
    
    def _create_minimal_index(self):
        """Create a minimal working index with just a few sample entries."""
        logger.info("🔧 Creating minimal fallback index...")
        
        try:
            writer = self.index.writer()
            try:
                # Add a sample entry to make the index functional
                writer.add_document(
                    file_path="fallback.py",
                    content="# Fallback search index",
                    line_content="# Fallback search index",
                    line_number=1,
                    language="python",
                    function_names="",
                    class_names="",
                    imports="",
                    variables="",
                    comments="Fallback search index",
                    file_hash="fallback"
                )
                writer.commit()
                logger.info("✅ Minimal fallback index created")
            except Exception as e:
                logger.error(f"Error creating minimal index: {e}")
                writer.cancel()
            finally:
                if hasattr(writer, 'is_closed') and not writer.is_closed():
                    writer.cancel()
        except Exception as e:
            logger.error(f"Failed to create minimal index: {e}")
    
    def _is_index_empty(self) -> bool:
        """Check if the search index is empty."""
        try:
            if not self.index:
                return True
            
            with self.index.searcher() as searcher:
                # Count documents in index
                doc_count = searcher.doc_count_all()
                return doc_count == 0
                
        except Exception as e:
            logger.warning(f"Error checking index status: {e}")
            return True
    
    def format_results(self, results: List[SearchResult], max_display: int = 20) -> str:
        """Format search results for display."""
        if not results:
            return "🔍 No matches found"
        
        output = [f"🔍 Found {len(results)} matches"]
        
        for i, result in enumerate(results[:max_display]):
            output.append(f"\n📄 {result.file_path}:{result.line_number} ({result.language}) [{result.match_type}] confidence: {result.confidence:.2f}")
            output.append(f"   {result.line_content}")
            
            if result.context_before:
                output.append("   Context:")
                for ctx in result.context_before[-1:]:  # Show last line of context
                    output.append(f"   - {ctx}")
            
        if len(results) > max_display:
            output.append(f"\n... and {len(results) - max_display} more results")
        
        return "\n".join(output)

# Backward compatibility wrapper for existing shell operations
class RobustAdvancedCodeSearch:
    """Backward-compatible wrapper for the Whoosh-based code search."""
    
    def __init__(self, repo_path: str):
        self.whoosh_search = WhooshCodeSearch(repo_path)
    
    def search_code(self, query: str, file_pattern: str = "*", context_lines: int = 3) -> Dict[str, Any]:
        """Search for code patterns with context - backward compatible method."""
        try:
            # Convert single pattern to list
            file_patterns = [file_pattern] if file_pattern != "*" else None
            
            results = self.whoosh_search.search_code_intelligent(
                query=query, 
                file_patterns=file_patterns,
                search_type="semantic"
            )
            
            formatted_output = self.whoosh_search.format_results(results)
            
            return {
                'success': True,
                'stdout': formatted_output,
                'stderr': '',
                'results': results
            }
        except Exception as e:
            return {
                'success': False,
                'stdout': '',
                'stderr': str(e),
                'results': []
            }
    
    def find_function_definitions(self, function_name: str, language: str = "auto") -> Dict[str, Any]:
        """Find function definitions - backward compatible method."""
        try:
            results = self.whoosh_search.find_functions_intelligent(function_name)
            formatted_output = self.whoosh_search.format_results(results)
            
            return {
                'success': True,
                'stdout': formatted_output,
                'stderr': '',
                'results': results
            }
        except Exception as e:
            return {
                'success': False,
                'stdout': '',
                'stderr': str(e),
                'results': []
            }
    
    def find_class_definitions(self, class_name: str, language: str = "auto") -> Dict[str, Any]:
        """Find class definitions - backward compatible method."""
        try:
            results = self.whoosh_search.find_classes_intelligent(class_name)
            formatted_output = self.whoosh_search.format_results(results)
            
            return {
                'success': True,
                'stdout': formatted_output,
                'stderr': '',
                'results': results
            }
        except Exception as e:
            return {
                'success': False,
                'stdout': '',
                'stderr': str(e),
                'results': []
            }
    
    def find_imports(self, module_name: str, language: str = "auto") -> Dict[str, Any]:
        """Find imports - backward compatible method."""
        try:
            results = self.whoosh_search.find_imports_intelligent(module_name)
            formatted_output = self.whoosh_search.format_results(results)
            
            return {
                'success': True,
                'stdout': formatted_output,
                'stderr': '',
                'results': results
            }
        except Exception as e:
            return {
                'success': False,
                'stdout': '',
                'stderr': str(e),
                'results': []
            }