"""
Fast CutySoup core implementation using the high-performance FastTree backend
"""

from typing import Dict, List, Optional, Union, Iterator, Pattern, Any
import re

try:
    from . import _cutysoup
except ImportError:
    try:
        import _cutysoup
    except ImportError:
        raise ImportError(
            "CutySoup C++ extension not found. Please run 'uv sync' to build the extension."
        )

from .exceptions import ParseError, NavigationError, CSQLError


class FastTag:
    """
    High-performance Tag implementation wrapping FastNode
    """
    
    def __init__(self, node: _cutysoup.Node):
        self._node = node
        self._cached_attrs = None
    
    @property
    def name(self) -> str:
        """Get the tag name."""
        return self._node.get_name()
    
    @property
    def attrs(self) -> Dict[str, str]:
        """Get all attributes as a dictionary."""
        if self._cached_attrs is None:
            self._cached_attrs = self._node.get_attributes()
        return self._cached_attrs
    
    def get(self, key: str, default=None):
        """Get attribute value with default."""
        return self.attrs.get(key, default)
    
    def __getitem__(self, key: str):
        """Get attribute value using dictionary-style access."""
        value = self.attrs.get(key)
        if value is None:
            raise KeyError(key)
        return value
    
    def has_attr(self, key: str) -> bool:
        """Check if attribute exists."""
        return key in self.attrs
    
    @property
    def text(self) -> str:
        """Get text content of tag and descendants."""
        return self._node.get_text()
    
    def get_text(self, separator: str = "", strip: bool = False) -> str:
        """Get text with options."""
        text = self._node.get_text()
        if strip:
            text = text.strip()
        return text
    
    @property
    def parent(self) -> Optional['FastTag']:
        """Get parent tag."""
        parent_node = self._node.get_parent()
        return FastTag(parent_node) if parent_node else None
    
    @property
    def children(self) -> List['FastTag']:
        """Get direct children."""
        child_nodes = self._node.get_children()
        return [FastTag(child) for child in child_nodes if not child.is_text()]
    
    @property
    def next_sibling(self) -> Optional['FastTag']:
        """Get next sibling."""
        next_node = self._node.get_next_sibling()
        return FastTag(next_node) if next_node else None
    
    @property
    def previous_sibling(self) -> Optional['FastTag']:
        """Get previous sibling."""
        prev_node = self._node.get_previous_sibling()
        return FastTag(prev_node) if prev_node else None
    
    def find(self, name: str = None, attrs: Dict = None, **kwargs) -> Optional['FastTag']:
        """Find first matching descendant tag."""
        results = self.find_all(name, attrs, limit=1, **kwargs)
        return results[0] if results else None
    
    def find_all(self, name: str = None, attrs: Dict = None, limit: Optional[int] = None, **kwargs) -> List['FastTag']:
        """Find all matching descendant tags."""
        # Use the high-performance C++ backend for common cases
        if name and not attrs and not kwargs:
            # Simple tag search
            nodes = self._node.find_descendants_by_tag(name)
        else:
            # Fall back to CSS selector for complex queries
            selector = self._build_css_selector(name, attrs, kwargs)
            if selector:
                nodes = self._node.select(selector)
            else:
                nodes = []
        
        results = [FastTag(node) for node in nodes]
        
        if limit and len(results) > limit:
            results = results[:limit]
        
        return results
    
    def select(self, selector: str) -> List['FastTag']:
        """Select elements using CSS selector within this node."""
        try:
            # Try the C++ implementation first
            nodes = self._node.select(selector)
            results = [FastTag(node) for node in nodes]
            
            # If C++ implementation returns empty but we have children, try fallback
            if not results and self.children:
                results = self._select_fallback(selector)
            
            return results
        except:
            # Fallback: manual traversal for simple selectors
            return self._select_fallback(selector)
    
    def select_one(self, selector: str) -> Optional['FastTag']:
        """Select first element using CSS selector."""
        node = self._node.select_one(selector)
        return FastTag(node) if node else None
    
    def _build_css_selector(self, name: str, attrs: Dict, kwargs: Dict) -> str:
        """Build CSS selector from BeautifulSoup4-style parameters."""
        parts = []
        
        if name:
            parts.append(name)
        
        # Handle attributes
        all_attrs = {}
        if attrs:
            all_attrs.update(attrs)
        all_attrs.update(kwargs)
        
        for attr_name, attr_value in all_attrs.items():
            if attr_name == 'class_':
                attr_name = 'class'
            
            if attr_value is True:
                parts.append(f'[{attr_name}]')
            elif isinstance(attr_value, str):
                if attr_name == 'class':
                    parts.append(f'.{attr_value}')
                elif attr_name == 'id':
                    parts.append(f'#{attr_value}')
                else:
                    parts.append(f'[{attr_name}="{attr_value}"]')
        
        return ''.join(parts)
    
    def _select_fallback(self, selector: str) -> List['FastTag']:
        """Fallback CSS selector implementation for simple cases."""
        results = []
        
        if selector.startswith('.'):
            # Class selector
            class_name = selector[1:]
            self._find_by_class_recursive(class_name, results)
        elif selector.startswith('#'):
            # ID selector
            id_value = selector[1:]
            self._find_by_id_recursive(id_value, results)
        elif selector and not selector.startswith('['):
            # Tag selector
            self._find_by_tag_recursive(selector, results)
        
        return results
    
    def _find_by_class_recursive(self, class_name: str, results: List['FastTag']):
        """Recursively find elements by class name."""
        # Check current node
        current_classes = self.get('class', '')
        if isinstance(current_classes, str):
            current_classes = current_classes.split()
        elif isinstance(current_classes, list):
            pass  # Already a list
        else:
            current_classes = []
            
        if class_name in current_classes:
            results.append(self)
        
        # Check children
        for child in self.children:
            child._find_by_class_recursive(class_name, results)
    
    def _find_by_id_recursive(self, id_value: str, results: List['FastTag']):
        """Recursively find elements by ID."""
        if self.get('id') == id_value:
            results.append(self)
        
        # Check children
        for child in self.children:
            child._find_by_id_recursive(id_value, results)
    
    def _find_by_tag_recursive(self, tag_name: str, results: List['FastTag']):
        """Recursively find elements by tag name."""
        if self.name == tag_name:
            results.append(self)
        
        # Check children
        for child in self.children:
            child._find_by_tag_recursive(tag_name, results)
    
    def __str__(self) -> str:
        """String representation."""
        # Simple implementation for now
        attrs_str = ' '.join(f'{k}="{v}"' for k, v in self.attrs.items())
        if attrs_str:
            return f"<{self.name} {attrs_str}>{self.text}</{self.name}>"
        else:
            return f"<{self.name}>{self.text}</{self.name}>"
    
    def __repr__(self) -> str:
        """Representation for debugging."""
        return f"<FastTag: {self.name}>"


class FastCutySoup:
    """
    High-performance CutySoup implementation using FastTree backend
    """
    
    def __init__(self, markup: Union[str, bytes] = "", features: str = "html.parser", **kwargs):
        """Initialize FastCutySoup parser."""
        
        # Convert bytes to string if necessary
        if isinstance(markup, bytes):
            try:
                markup = markup.decode('utf-8')
            except UnicodeDecodeError:
                # Try common encodings
                for encoding in ['latin1', 'windows-1252', 'iso-8859-1']:
                    try:
                        markup = markup.decode(encoding)
                        break
                    except UnicodeDecodeError:
                        continue
                else:
                    # Last resort - decode with errors ignored
                    markup = markup.decode('utf-8', errors='ignore')
        
        try:
            # Use the high-performance FastTree backend
            self._tree = _cutysoup.Tree(markup or "")
            self._original_markup = markup
        except Exception as e:
            raise ParseError(f"Failed to parse HTML: {e}")
    
    @property
    def name(self) -> str:
        """Get document name (always '[document]')."""
        return '[document]'
    
    def find(self, name: str = None, attrs: Dict = None, **kwargs) -> Optional[FastTag]:
        """Find first matching tag in document."""
        results = self.find_all(name, attrs, limit=1, **kwargs)
        return results[0] if results else None
    
    def find_all(self, name: str = None, attrs: Dict = None, limit: Optional[int] = None, **kwargs) -> List[FastTag]:
        """Find all matching tags in document."""
        # Build CSS selector for all cases to ensure consistency
        selector = self._build_css_selector(name, attrs, kwargs)
        
        if selector:
            try:
                nodes = self._tree.select(selector)
            except:
                # Fallback to root-level search if CSS selector fails
                root = self._get_root_tag()
                if root:
                    return root.find_all(name, attrs, limit=limit, **kwargs)
                else:
                    return []
        else:
            # No selector built, return empty
            return []
        
        results = [FastTag(node) for node in nodes]
        
        if limit and len(results) > limit:
            results = results[:limit]
        
        return results
    
    def select(self, selector: str) -> List[FastTag]:
        """Select elements using CSS selector."""
        nodes = self._tree.select(selector)
        return [FastTag(node) for node in nodes]
    
    def select_one(self, selector: str) -> Optional[FastTag]:
        """Select first element using CSS selector."""
        node = self._tree.select_one(selector)
        return FastTag(node) if node else None
    
    @property
    def text(self) -> str:
        """Get all text content from document."""
        root = self._get_root_tag()
        return root.text if root else ""
    
    def get_text(self, separator: str = "", strip: bool = False) -> str:
        """Get text with separator and strip options."""
        root = self._get_root_tag()
        return root.get_text(separator, strip) if root else ""
    
    def _get_root_tag(self) -> Optional[FastTag]:
        """Get root tag of document."""
        try:
            root_node = self._tree.get_root()
            return FastTag(root_node) if root_node else None
        except Exception:
            return None
    
    def _build_css_selector(self, name: str, attrs: Dict, kwargs: Dict) -> str:
        """Build CSS selector from BeautifulSoup4-style parameters."""
        parts = []
        
        if name:
            parts.append(name)
        
        # Handle attributes
        all_attrs = {}
        if attrs:
            all_attrs.update(attrs)
        all_attrs.update(kwargs)
        
        for attr_name, attr_value in all_attrs.items():
            if attr_name == 'class_':
                attr_name = 'class'
            
            if attr_value is True:
                parts.append(f'[{attr_name}]')
            elif isinstance(attr_value, str):
                if attr_name == 'class':
                    parts.append(f'.{attr_value}')
                elif attr_name == 'id':
                    parts.append(f'#{attr_value}')
                else:
                    parts.append(f'[{attr_name}="{attr_value}"]')
        
        return ''.join(parts)
    
    # CSQL Integration
    def query(self, query_plan: Union[str, Dict]) -> Dict[str, Any]:
        """
        Execute a CSQL (CutySoup Query Language) query for structured data extraction.
        """
        from .csql import CSQLEngine
        
        # Create CSQL engine with FastTree backend adapter
        engine = FastCSQLEngine(self)
        return engine.execute_query(query_plan)
    
    # High-performance bulk operations
    def get_all_hrefs(self) -> List[str]:
        """Get all href attributes from anchor tags (high-performance)."""
        return self._tree.extract_all_hrefs()
    
    def get_all_src_attributes(self) -> List[str]:
        """Get all src attributes from img tags (high-performance)."""
        return self._tree.extract_all_src()
    
    def get_all_text_by_tag(self, tag_name: str) -> List[str]:
        """Get all text content for elements with specified tag (high-performance)."""
        return self._tree.extract_all_text_by_tag(tag_name)
    
    def get_node_count(self) -> int:
        """Get total number of nodes in the document."""
        return self._tree.get_node_count()
    
    def __str__(self) -> str:
        """String representation of document."""
        root = self._get_root_tag()
        return str(root) if root else ""
    
    def __repr__(self) -> str:
        """Representation for debugging."""
        return f"<FastCutySoup: {len(self._original_markup)} chars, {self.get_node_count()} nodes>"


class FastCSQLEngine:
    """
    CSQL engine adapted for FastTree backend
    """
    
    def __init__(self, soup: FastCutySoup):
        self.soup = soup
    
    def execute_query(self, query_plan: Union[str, Dict]) -> Dict[str, Any]:
        """Execute CSQL query using FastTree backend."""
        from .csql import CSQLEngine
        
        # Create adapter that makes FastCutySoup look like the old CutySoup for CSQL
        adapter = FastCutySoupAdapter(self.soup)
        engine = CSQLEngine(adapter)
        
        return engine.execute_query(query_plan)


class FastCutySoupAdapter:
    """
    Adapter to make FastCutySoup compatible with CSQL engine
    """
    
    def __init__(self, fast_soup: FastCutySoup):
        self._fast_soup = fast_soup
    
    def select(self, selector: str) -> List:
        """Select elements using CSS selector - CSQL compatible."""
        # Return FastTag objects that have get_text() and get() methods
        return self._fast_soup.select(selector)
    
    def find_all(self, *args, **kwargs):
        """Find all elements - CSQL compatible."""
        return self._fast_soup.find_all(*args, **kwargs)