"""
Text processing and extraction functionality for CutySoup
"""
from typing import Iterator, List, TYPE_CHECKING
import re
from .utils import normalize_whitespace

if TYPE_CHECKING:
    from .core import Tag


class TextProcessor:
    """Text processing utilities for HTML content."""
    
    @staticmethod
    def extract_text_from_tag(tag: 'Tag', separator: str = "", strip: bool = False, 
                             types: tuple = (str,)) -> str:
        """Extract text content from a tag and its descendants."""
        if not hasattr(tag, 'contents'):
            return ""
        
        text_parts = []
        
        def _extract_recursive(element, depth=0):
            if hasattr(element, 'contents'):  # It's a tag
                # Check if this is a block-level element
                block_elements = {'p', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'li', 'ul', 'ol', 'blockquote', 'article', 'section', 'header', 'footer', 'aside', 'nav', 'main'}
                is_block = getattr(element, 'name', '').lower() in block_elements
                
                # For elements that have multiple block children, add spacing between them
                if hasattr(element, 'contents') and len(element.contents) > 1:
                    block_children = [child for child in element.contents if hasattr(child, 'name') and child.name and child.name.lower() in block_elements]
                    
                    for i, child in enumerate(element.contents):
                        _extract_recursive(child, depth + 1)
                        
                        # Add newlines between block-level siblings (but not after the last one)
                        if (i < len(element.contents) - 1 and 
                            hasattr(child, 'name') and child.name and child.name.lower() in block_elements and
                            len(block_children) > 1):
                            text_parts.append('\n')
                else:
                    # Single child or no contents
                    for child in element.contents:
                        _extract_recursive(child, depth + 1)
                        
            elif isinstance(element, types):  # It's a string
                text_parts.append(str(element))
        
        _extract_recursive(tag)
        
        if strip:
            text_parts = [part.strip() for part in text_parts if part.strip()]
        
        result = separator.join(text_parts)
        return result.strip() if strip else result
    
    @staticmethod
    def get_strings_from_tag(tag: 'Tag') -> Iterator[str]:
        """Get all string contents from tag and descendants."""
        if not hasattr(tag, 'contents'):
            return
        
        def _get_strings_recursive(element):
            if hasattr(element, 'contents'):  # It's a tag
                for child in element.contents:
                    yield from _get_strings_recursive(child)
            elif isinstance(element, str):  # It's a string
                yield element
        
        yield from _get_strings_recursive(tag)
    
    @staticmethod
    def get_stripped_strings_from_tag(tag: 'Tag') -> Iterator[str]:
        """Get all non-empty string contents from tag and descendants."""
        for string in TextProcessor.get_strings_from_tag(tag):
            stripped = string.strip()
            if stripped:
                yield stripped
    
    @staticmethod
    def get_filtered_strings(tag: 'Tag', min_length: int = 1) -> Iterator[str]:
        """Get strings with minimum length filter."""
        for string in TextProcessor.get_stripped_strings_from_tag(tag):
            if len(string) >= min_length:
                yield string
    
    @staticmethod
    def find_all_text_occurrences(tag: 'Tag', search_text: str, 
                                 case_sensitive: bool = True) -> List[str]:
        """Find all occurrences of text within tag contents."""
        if not search_text:
            return []
        
        results = []
        text_content = TextProcessor.extract_text_from_tag(tag)
        
        if not case_sensitive:
            search_text = search_text.lower()
            text_content = text_content.lower()
        
        # Find all occurrences
        start = 0
        while True:
            pos = text_content.find(search_text, start)
            if pos == -1:
                break
            results.append(search_text)
            start = pos + 1
        
        return results
    
    @staticmethod
    def replace_text_in_tag(tag: 'Tag', old_text: str, new_text: str, 
                           count: int = -1) -> int:
        """Replace text occurrences within tag contents."""
        if not hasattr(tag, 'contents') or not old_text:
            return 0
        
        replacements_made = 0
        remaining_count = count
        
        def _replace_in_contents(contents_list):
            nonlocal replacements_made, remaining_count
            
            for i, content in enumerate(contents_list):
                if remaining_count == 0:
                    break
                
                if isinstance(content, str):
                    if remaining_count == -1:
                        new_content = content.replace(old_text, new_text)
                        replacements_made += content.count(old_text)
                    else:
                        # Handle limited count replacements
                        parts = content.split(old_text)
                        if len(parts) > 1:
                            replacements_to_make = min(len(parts) - 1, remaining_count)
                            new_content = new_text.join(parts[:replacements_to_make + 1])
                            if replacements_to_make < len(parts) - 1:
                                new_content += old_text + old_text.join(parts[replacements_to_make + 1:])
                            replacements_made += replacements_to_make
                            remaining_count -= replacements_to_make
                        else:
                            new_content = content
                    
                    contents_list[i] = new_content
                
                elif hasattr(content, 'contents'):
                    _replace_in_contents(content.contents)
        
        _replace_in_contents(tag.contents)
        return replacements_made
    
    @staticmethod
    def get_direct_text(tag: 'Tag') -> str:
        """Get only the direct text content of a tag (not from descendants)."""
        if not hasattr(tag, 'contents'):
            return ""
        
        direct_texts = []
        for content in tag.contents:
            if isinstance(content, str):
                direct_texts.append(content)
        
        return "".join(direct_texts)
    
    @staticmethod
    def normalize_whitespace_in_tag(tag: 'Tag', separator: str = " "):
        """Normalize whitespace in all text content within a tag."""
        if not hasattr(tag, 'contents'):
            return
        
        def _normalize_recursive(contents_list):
            for i, content in enumerate(contents_list):
                if isinstance(content, str):
                    contents_list[i] = normalize_whitespace(content, separator)
                elif hasattr(content, 'contents'):
                    _normalize_recursive(content.contents)
        
        _normalize_recursive(tag.contents)
    
    @staticmethod
    def extract_links(tag: 'Tag') -> List[dict]:
        """Extract all links from tag and its descendants."""
        links = []
        
        def _extract_links_recursive(element):
            if hasattr(element, 'name'):
                if element.name == 'a' and element.get('href'):
                    links.append({
                        'url': element.get('href'),
                        'text': element.get_text(strip=True),
                        'title': element.get('title', ''),
                        'tag': element
                    })
                
                # Also check for other link-like elements
                elif element.name == 'link' and element.get('href'):
                    links.append({
                        'url': element.get('href'),
                        'rel': element.get('rel', ''),
                        'type': element.get('type', ''),
                        'tag': element
                    })
                
                # Recurse into children
                if hasattr(element, 'contents'):
                    for child in element.contents:
                        _extract_links_recursive(child)
        
        _extract_links_recursive(tag)
        return links
    
    @staticmethod
    def extract_images(tag: 'Tag') -> List[dict]:
        """Extract all images from tag and its descendants."""
        images = []
        
        def _extract_images_recursive(element):
            if hasattr(element, 'name') and element.name == 'img':
                images.append({
                    'src': element.get('src', ''),
                    'alt': element.get('alt', ''),
                    'title': element.get('title', ''),
                    'width': element.get('width', ''),
                    'height': element.get('height', ''),
                    'tag': element
                })
            
            # Recurse into children
            if hasattr(element, 'contents'):
                for child in element.contents:
                    _extract_images_recursive(child)
        
        _extract_images_recursive(tag)
        return images
    
    @staticmethod
    def get_text_statistics(tag: 'Tag') -> dict:
        """Get text statistics for a tag."""
        text = TextProcessor.extract_text_from_tag(tag, strip=True)
        words = text.split() if text else []
        
        return {
            'character_count': len(text),
            'word_count': len(words),
            'line_count': text.count('\n') + 1 if text else 0,
            'paragraph_count': len([p for p in text.split('\n\n') if p.strip()]),
            'average_word_length': sum(len(word) for word in words) / len(words) if words else 0
        }
    
    @staticmethod
    def clean_text(text: str, 
                   remove_extra_whitespace: bool = True,
                   remove_line_breaks: bool = False,
                   remove_punctuation: bool = False) -> str:
        """Clean and normalize text content."""
        if not isinstance(text, str):
            text = str(text)
        
        if remove_extra_whitespace:
            text = normalize_whitespace(text)
        
        if remove_line_breaks:
            text = text.replace('\n', ' ').replace('\r', ' ')
            if remove_extra_whitespace:
                text = normalize_whitespace(text)
        
        if remove_punctuation:
            # Remove common punctuation
            text = re.sub(r'[^\w\s]', '', text)
            if remove_extra_whitespace:
                text = normalize_whitespace(text)
        
        return text.strip()
    
    @staticmethod
    def extract_headings(tag: 'Tag') -> List[dict]:
        """Extract all heading tags (h1-h6) with their levels and text."""
        headings = []
        
        def _extract_headings_recursive(element):
            if hasattr(element, 'name') and element.name:
                if element.name.lower() in ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']:
                    level = int(element.name.lower()[1])
                    headings.append({
                        'level': level,
                        'text': element.get_text(strip=True),
                        'id': element.get('id', ''),
                        'tag': element
                    })
                
                # Recurse into children
                if hasattr(element, 'contents'):
                    for child in element.contents:
                        _extract_headings_recursive(child)
        
        _extract_headings_recursive(tag)
        return headings
    
    @staticmethod
    def extract_tables(tag: 'Tag') -> List[dict]:
        """Extract table data with basic structure."""
        tables = []
        
        def _extract_table_data(table_tag):
            rows = []
            
            # Find all tr elements
            for row_tag in table_tag.find_all('tr'):
                cells = []
                
                # Find all td and th elements
                for cell_tag in row_tag.find_all(['td', 'th']):
                    cells.append({
                        'text': cell_tag.get_text(strip=True),
                        'is_header': cell_tag.name == 'th',
                        'colspan': int(cell_tag.get('colspan', 1)),
                        'rowspan': int(cell_tag.get('rowspan', 1))
                    })
                
                if cells:
                    rows.append(cells)
            
            return {
                'rows': rows,
                'caption': table_tag.find('caption').get_text(strip=True) if table_tag.find('caption') else '',
                'tag': table_tag
            }
        
        def _find_tables_recursive(element):
            if hasattr(element, 'name') and element.name == 'table':
                tables.append(_extract_table_data(element))
            
            # Recurse into children
            if hasattr(element, 'contents'):
                for child in element.contents:
                    _find_tables_recursive(child)
        
        _find_tables_recursive(tag)
        return tables