"""
Document ingestion service for parsing various document formats.

Supports:
- PDF (with math extraction)
- EPUB
- HTML
- LaTeX source files
- Markdown
"""

import re
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple, Union
from enum import Enum

from ..infra.logging import get_logger
from ..infra.tracing import trace_function

logger = get_logger(__name__)


class DocumentFormat(Enum):
    """Supported document formats."""
    PDF = "pdf"
    EPUB = "epub"
    HTML = "html"
    LATEX = "latex"
    MARKDOWN = "markdown"
    TEXT = "text"
    UNKNOWN = "unknown"


@dataclass
class DocumentMetadata:
    """Metadata extracted from a document."""
    title: Optional[str] = None
    authors: List[str] = field(default_factory=list)
    publication_date: Optional[str] = None
    isbn: Optional[str] = None
    doi: Optional[str] = None
    language: str = "en"
    page_count: Optional[int] = None
    word_count: Optional[int] = None
    source_format: DocumentFormat = DocumentFormat.UNKNOWN
    source_path: Optional[str] = None
    custom: Dict[str, Any] = field(default_factory=dict)


@dataclass
class ContentBlock:
    """Represents a block of content from a document."""
    content: str
    block_type: str  # "text", "heading", "math", "code", "table", "figure", "list"
    page_number: Optional[int] = None
    section_hierarchy: List[str] = field(default_factory=list)  # ["Chapter 1", "Section 1.2"]
    metadata: Dict[str, Any] = field(default_factory=dict)
    
    @property
    def is_math(self) -> bool:
        return self.block_type == "math"
    
    @property
    def is_code(self) -> bool:
        return self.block_type == "code"
    
    @property
    def is_heading(self) -> bool:
        return self.block_type == "heading"


@dataclass
class ParsedDocument:
    """Represents a fully parsed document."""
    metadata: DocumentMetadata
    content_blocks: List[ContentBlock]
    raw_text: str
    table_of_contents: List[Dict[str, Any]] = field(default_factory=list)
    
    @property
    def full_text(self) -> str:
        """Get concatenated text content."""
        return "\n\n".join(block.content for block in self.content_blocks)
    
    def get_blocks_by_type(self, block_type: str) -> List[ContentBlock]:
        """Filter blocks by type."""
        return [b for b in self.content_blocks if b.block_type == block_type]
    
    def get_math_blocks(self) -> List[ContentBlock]:
        """Get all math blocks."""
        return self.get_blocks_by_type("math")
    
    def get_code_blocks(self) -> List[ContentBlock]:
        """Get all code blocks."""
        return self.get_blocks_by_type("code")


class DocumentParser(ABC):
    """Abstract base class for document parsers."""
    
    @abstractmethod
    def parse(self, file_path: Path) -> ParsedDocument:
        """Parse a document and return structured content."""
        pass
    
    @abstractmethod
    def supports(self, file_path: Path) -> bool:
        """Check if this parser supports the given file."""
        pass


class PDFParser(DocumentParser):
    """Parser for PDF documents."""
    
    # Regex patterns for math detection
    MATH_PATTERNS = [
        r'\$\$.*?\$\$',  # Display math
        r'\$[^$]+\$',     # Inline math
        r'\\begin\{equation\}.*?\\end\{equation\}',
        r'\\begin\{align\}.*?\\end\{align\}',
        r'\\begin\{eqnarray\}.*?\\end\{eqnarray\}',
        r'\\[.*?\\]',     # LaTeX display math
        r'\\\(.*?\\\)',   # LaTeX inline math
    ]
    
    def __init__(self, extract_images: bool = False, ocr_enabled: bool = False):
        self.extract_images = extract_images
        self.ocr_enabled = ocr_enabled
    
    def supports(self, file_path: Path) -> bool:
        return file_path.suffix.lower() == ".pdf"
    
    @trace_function(name="pdf_parse")
    def parse(self, file_path: Path) -> ParsedDocument:
        """Parse a PDF document."""
        try:
            import pypdf
        except ImportError:
            raise ImportError("pypdf is required for PDF parsing. Install with: pip install pypdf")
        
        logger.info(f"Parsing PDF: {file_path}")
        
        content_blocks = []
        raw_text_parts = []
        toc = []
        
        with open(file_path, "rb") as f:
            reader = pypdf.PdfReader(f)
            
            # Extract metadata
            pdf_meta = reader.metadata or {}
            metadata = DocumentMetadata(
                title=pdf_meta.get("/Title"),
                authors=[pdf_meta.get("/Author")] if pdf_meta.get("/Author") else [],
                page_count=len(reader.pages),
                source_format=DocumentFormat.PDF,
                source_path=str(file_path)
            )
            
            # Extract table of contents if available
            if reader.outline:
                toc = self._extract_toc(reader.outline)
            
            # Extract content page by page
            current_section = []
            
            for page_num, page in enumerate(reader.pages, 1):
                text = page.extract_text() or ""
                raw_text_parts.append(text)
                
                # Parse content blocks from page
                page_blocks = self._parse_page_content(text, page_num, current_section)
                content_blocks.extend(page_blocks)
                
                # Update section hierarchy from headings
                for block in page_blocks:
                    if block.is_heading:
                        current_section = block.section_hierarchy
        
        # Calculate word count
        raw_text = "\n\n".join(raw_text_parts)
        metadata.word_count = len(raw_text.split())
        
        logger.info(f"Parsed PDF with {len(content_blocks)} content blocks")
        
        return ParsedDocument(
            metadata=metadata,
            content_blocks=content_blocks,
            raw_text=raw_text,
            table_of_contents=toc
        )
    
    def _extract_toc(self, outline: list, level: int = 0) -> List[Dict[str, Any]]:
        """Extract table of contents from PDF outline."""
        toc = []
        for item in outline:
            if isinstance(item, list):
                toc.extend(self._extract_toc(item, level + 1))
            else:
                toc.append({
                    "title": item.title if hasattr(item, "title") else str(item),
                    "level": level
                })
        return toc
    
    def _parse_page_content(
        self, 
        text: str, 
        page_num: int, 
        current_section: List[str]
    ) -> List[ContentBlock]:
        """Parse page text into content blocks."""
        blocks = []
        
        # Split into paragraphs
        paragraphs = re.split(r'\n\s*\n', text)
        
        for para in paragraphs:
            para = para.strip()
            if not para:
                continue
            
            # Detect block type
            block_type = self._detect_block_type(para)
            
            # Check if it's a heading (update section hierarchy)
            section_hierarchy = current_section.copy()
            if block_type == "heading":
                section_hierarchy = current_section + [para]
            
            blocks.append(ContentBlock(
                content=para,
                block_type=block_type,
                page_number=page_num,
                section_hierarchy=section_hierarchy
            ))
        
        return blocks
    
    def _detect_block_type(self, text: str) -> str:
        """Detect the type of content block."""
        # Check for math
        for pattern in self.MATH_PATTERNS:
            if re.search(pattern, text, re.DOTALL):
                return "math"
        
        # Check for code (common indicators)
        code_indicators = [
            r'^\s*(def |class |import |from |if |for |while |return )',
            r'^\s*```',
            r'^\s*#include',
            r'^\s*function\s+\w+',
            r'Algorithm \d+',
        ]
        for pattern in code_indicators:
            if re.search(pattern, text, re.MULTILINE):
                return "code"
        
        # Check for heading (short, possibly numbered, ends without punctuation)
        if len(text) < 100 and not text.endswith(('.', '?', '!')):
            if re.match(r'^(\d+\.?)+\s+\w', text) or re.match(r'^(Chapter|Section|Part)\s+', text, re.I):
                return "heading"
        
        # Check for list
        if re.match(r'^\s*[-•*]\s', text) or re.match(r'^\s*\d+\.\s', text):
            return "list"
        
        return "text"


class EPUBParser(DocumentParser):
    """Parser for EPUB documents."""
    
    def supports(self, file_path: Path) -> bool:
        return file_path.suffix.lower() == ".epub"
    
    @trace_function(name="epub_parse")
    def parse(self, file_path: Path) -> ParsedDocument:
        """Parse an EPUB document."""
        try:
            from ebooklib import epub
            from bs4 import BeautifulSoup
        except ImportError:
            raise ImportError(
                "ebooklib and beautifulsoup4 are required for EPUB parsing. "
                "Install with: pip install ebooklib beautifulsoup4"
            )
        
        logger.info(f"Parsing EPUB: {file_path}")
        
        book = epub.read_epub(str(file_path))
        
        # Extract metadata
        metadata = DocumentMetadata(
            title=book.get_metadata('DC', 'title')[0][0] if book.get_metadata('DC', 'title') else None,
            authors=[a[0] for a in book.get_metadata('DC', 'creator')] if book.get_metadata('DC', 'creator') else [],
            language=book.get_metadata('DC', 'language')[0][0] if book.get_metadata('DC', 'language') else "en",
            source_format=DocumentFormat.EPUB,
            source_path=str(file_path)
        )
        
        content_blocks = []
        raw_text_parts = []
        toc = []
        
        # Extract TOC
        for item in book.toc:
            if hasattr(item, 'title'):
                toc.append({"title": item.title, "level": 0})
        
        # Extract content from each document
        for item in book.get_items():
            if item.get_type() == epub.ITEM_DOCUMENT:
                content = item.get_content().decode('utf-8', errors='ignore')
                soup = BeautifulSoup(content, 'html.parser')
                
                # Extract text and structure
                blocks = self._parse_html_content(soup)
                content_blocks.extend(blocks)
                raw_text_parts.append(soup.get_text())
        
        raw_text = "\n\n".join(raw_text_parts)
        metadata.word_count = len(raw_text.split())
        
        logger.info(f"Parsed EPUB with {len(content_blocks)} content blocks")
        
        return ParsedDocument(
            metadata=metadata,
            content_blocks=content_blocks,
            raw_text=raw_text,
            table_of_contents=toc
        )
    
    def _parse_html_content(self, soup) -> List[ContentBlock]:
        """Parse HTML content into blocks."""
        blocks = []
        
        # Process headings
        for level in range(1, 7):
            for heading in soup.find_all(f'h{level}'):
                blocks.append(ContentBlock(
                    content=heading.get_text().strip(),
                    block_type="heading",
                    metadata={"level": level}
                ))
        
        # Process paragraphs
        for p in soup.find_all('p'):
            text = p.get_text().strip()
            if text:
                blocks.append(ContentBlock(
                    content=text,
                    block_type="text"
                ))
        
        # Process code blocks
        for code in soup.find_all(['code', 'pre']):
            text = code.get_text().strip()
            if text:
                blocks.append(ContentBlock(
                    content=text,
                    block_type="code"
                ))
        
        return blocks


class HTMLParser(DocumentParser):
    """Parser for HTML documents."""
    
    def supports(self, file_path: Path) -> bool:
        return file_path.suffix.lower() in [".html", ".htm"]
    
    @trace_function(name="html_parse")
    def parse(self, file_path: Path) -> ParsedDocument:
        """Parse an HTML document."""
        try:
            from bs4 import BeautifulSoup
        except ImportError:
            raise ImportError("beautifulsoup4 is required. Install with: pip install beautifulsoup4")
        
        logger.info(f"Parsing HTML: {file_path}")
        
        with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
            content = f.read()
        
        soup = BeautifulSoup(content, 'html.parser')
        
        # Extract metadata
        title = soup.title.string if soup.title else None
        metadata = DocumentMetadata(
            title=title,
            source_format=DocumentFormat.HTML,
            source_path=str(file_path)
        )
        
        # Parse content
        content_blocks = self._parse_html_structure(soup)
        raw_text = soup.get_text()
        metadata.word_count = len(raw_text.split())
        
        logger.info(f"Parsed HTML with {len(content_blocks)} content blocks")
        
        return ParsedDocument(
            metadata=metadata,
            content_blocks=content_blocks,
            raw_text=raw_text
        )
    
    def _parse_html_structure(self, soup) -> List[ContentBlock]:
        """Parse HTML structure into content blocks."""
        blocks = []
        
        # Find main content area
        main_content = soup.find('main') or soup.find('article') or soup.find('body') or soup
        
        for element in main_content.find_all(['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'pre', 'code', 'ul', 'ol', 'table']):
            text = element.get_text().strip()
            if not text:
                continue
            
            if element.name.startswith('h'):
                block_type = "heading"
            elif element.name in ['pre', 'code']:
                block_type = "code"
            elif element.name in ['ul', 'ol']:
                block_type = "list"
            elif element.name == 'table':
                block_type = "table"
            else:
                block_type = "text"
            
            blocks.append(ContentBlock(
                content=text,
                block_type=block_type,
                metadata={"tag": element.name}
            ))
        
        return blocks


class LaTeXParser(DocumentParser):
    """Parser for LaTeX source files."""
    
    # Regex patterns for LaTeX structure
    SECTION_PATTERNS = [
        (r'\\chapter\{([^}]+)\}', 0),
        (r'\\section\{([^}]+)\}', 1),
        (r'\\subsection\{([^}]+)\}', 2),
        (r'\\subsubsection\{([^}]+)\}', 3),
    ]
    
    MATH_ENVIRONMENTS = [
        'equation', 'equation*', 'align', 'align*', 'eqnarray', 'eqnarray*',
        'gather', 'gather*', 'multline', 'multline*', 'displaymath'
    ]
    
    CODE_ENVIRONMENTS = [
        'lstlisting', 'verbatim', 'minted', 'algorithm', 'algorithmic'
    ]
    
    def supports(self, file_path: Path) -> bool:
        return file_path.suffix.lower() in [".tex", ".latex"]
    
    @trace_function(name="latex_parse")
    def parse(self, file_path: Path) -> ParsedDocument:
        """Parse a LaTeX source file."""
        logger.info(f"Parsing LaTeX: {file_path}")
        
        with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
            content = f.read()
        
        # Extract metadata
        metadata = self._extract_latex_metadata(content, file_path)
        
        # Parse content blocks
        content_blocks = self._parse_latex_content(content)
        
        # Generate raw text (strip LaTeX commands)
        raw_text = self._strip_latex_commands(content)
        metadata.word_count = len(raw_text.split())
        
        # Extract TOC from section commands
        toc = self._extract_toc(content)
        
        logger.info(f"Parsed LaTeX with {len(content_blocks)} content blocks")
        
        return ParsedDocument(
            metadata=metadata,
            content_blocks=content_blocks,
            raw_text=raw_text,
            table_of_contents=toc
        )
    
    def _extract_latex_metadata(self, content: str, file_path: Path) -> DocumentMetadata:
        """Extract metadata from LaTeX document."""
        title = None
        authors = []
        
        # Extract title
        title_match = re.search(r'\\title\{([^}]+)\}', content)
        if title_match:
            title = title_match.group(1)
        
        # Extract authors
        author_match = re.search(r'\\author\{([^}]+)\}', content)
        if author_match:
            author_text = author_match.group(1)
            # Split by 'and' or '\\'
            authors = re.split(r'\s+and\s+|\\\\', author_text)
            authors = [a.strip() for a in authors if a.strip()]
        
        return DocumentMetadata(
            title=title,
            authors=authors,
            source_format=DocumentFormat.LATEX,
            source_path=str(file_path)
        )
    
    def _parse_latex_content(self, content: str) -> List[ContentBlock]:
        """Parse LaTeX content into blocks."""
        blocks = []
        current_section = []
        
        # Split content into segments
        lines = content.split('\n')
        current_text = []
        
        for line in lines:
            # Check for section commands
            for pattern, level in self.SECTION_PATTERNS:
                match = re.search(pattern, line)
                if match:
                    # Save accumulated text
                    if current_text:
                        text = '\n'.join(current_text).strip()
                        if text:
                            blocks.append(ContentBlock(
                                content=text,
                                block_type="text",
                                section_hierarchy=current_section.copy()
                            ))
                        current_text = []
                    
                    # Update section hierarchy
                    section_title = match.group(1)
                    current_section = current_section[:level] + [section_title]
                    
                    blocks.append(ContentBlock(
                        content=section_title,
                        block_type="heading",
                        section_hierarchy=current_section.copy(),
                        metadata={"level": level}
                    ))
                    break
            else:
                current_text.append(line)
        
        # Process remaining text
        if current_text:
            text = '\n'.join(current_text).strip()
            if text:
                blocks.append(ContentBlock(
                    content=text,
                    block_type="text",
                    section_hierarchy=current_section.copy()
                ))
        
        # Extract math environments
        blocks.extend(self._extract_math_blocks(content, current_section))
        
        # Extract code environments
        blocks.extend(self._extract_code_blocks(content, current_section))
        
        return blocks
    
    def _extract_math_blocks(self, content: str, section: List[str]) -> List[ContentBlock]:
        """Extract math environments from LaTeX."""
        blocks = []
        
        for env in self.MATH_ENVIRONMENTS:
            pattern = rf'\\begin\{{{env}\}}(.*?)\\end\{{{env}\}}'
            for match in re.finditer(pattern, content, re.DOTALL):
                blocks.append(ContentBlock(
                    content=match.group(0),
                    block_type="math",
                    section_hierarchy=section.copy(),
                    metadata={"environment": env}
                ))
        
        # Also extract display math
        for match in re.finditer(r'\$\$(.*?)\$\$', content, re.DOTALL):
            blocks.append(ContentBlock(
                content=match.group(0),
                block_type="math",
                section_hierarchy=section.copy()
            ))
        
        return blocks
    
    def _extract_code_blocks(self, content: str, section: List[str]) -> List[ContentBlock]:
        """Extract code environments from LaTeX."""
        blocks = []
        
        for env in self.CODE_ENVIRONMENTS:
            pattern = rf'\\begin\{{{env}\}}(.*?)\\end\{{{env}\}}'
            for match in re.finditer(pattern, content, re.DOTALL):
                blocks.append(ContentBlock(
                    content=match.group(1).strip(),
                    block_type="code",
                    section_hierarchy=section.copy(),
                    metadata={"environment": env}
                ))
        
        return blocks
    
    def _extract_toc(self, content: str) -> List[Dict[str, Any]]:
        """Extract table of contents from section commands."""
        toc = []
        
        for pattern, level in self.SECTION_PATTERNS:
            for match in re.finditer(pattern, content):
                toc.append({
                    "title": match.group(1),
                    "level": level
                })
        
        return toc
    
    def _strip_latex_commands(self, content: str) -> str:
        """Remove LaTeX commands to get plain text."""
        # Remove comments
        text = re.sub(r'%.*$', '', content, flags=re.MULTILINE)
        
        # Remove common commands but keep content
        text = re.sub(r'\\(textbf|textit|emph|underline)\{([^}]+)\}', r'\2', text)
        text = re.sub(r'\\(section|subsection|subsubsection|chapter|title|author)\{([^}]+)\}', r'\2', text)
        
        # Remove other commands
        text = re.sub(r'\\[a-zA-Z]+\{[^}]*\}', '', text)
        text = re.sub(r'\\[a-zA-Z]+', '', text)
        
        # Clean up
        text = re.sub(r'\{|\}', '', text)
        text = re.sub(r'\s+', ' ', text)
        
        return text.strip()


class MarkdownParser(DocumentParser):
    """Parser for Markdown documents."""
    
    def supports(self, file_path: Path) -> bool:
        return file_path.suffix.lower() in [".md", ".markdown"]
    
    @trace_function(name="markdown_parse")
    def parse(self, file_path: Path) -> ParsedDocument:
        """Parse a Markdown document."""
        logger.info(f"Parsing Markdown: {file_path}")
        
        with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
            content = f.read()
        
        # Extract metadata from YAML frontmatter if present
        metadata, content = self._extract_frontmatter(content, file_path)
        
        # Parse content blocks
        content_blocks = self._parse_markdown_content(content)
        
        # Raw text
        raw_text = re.sub(r'[#*_`\[\]()]', '', content)
        metadata.word_count = len(raw_text.split())
        
        # Extract TOC from headings
        toc = self._extract_toc(content)
        
        logger.info(f"Parsed Markdown with {len(content_blocks)} content blocks")
        
        return ParsedDocument(
            metadata=metadata,
            content_blocks=content_blocks,
            raw_text=raw_text,
            table_of_contents=toc
        )
    
    def _extract_frontmatter(self, content: str, file_path: Path) -> Tuple[DocumentMetadata, str]:
        """Extract YAML frontmatter from Markdown."""
        metadata = DocumentMetadata(
            source_format=DocumentFormat.MARKDOWN,
            source_path=str(file_path)
        )
        
        if content.startswith('---'):
            end_match = re.search(r'\n---\n', content[3:])
            if end_match:
                try:
                    import yaml
                    frontmatter = yaml.safe_load(content[3:end_match.start() + 3])
                    if isinstance(frontmatter, dict):
                        metadata.title = frontmatter.get('title')
                        authors = frontmatter.get('author') or frontmatter.get('authors')
                        if authors:
                            metadata.authors = authors if isinstance(authors, list) else [authors]
                    content = content[end_match.end() + 6:]
                except:
                    pass
        
        return metadata, content
    
    def _parse_markdown_content(self, content: str) -> List[ContentBlock]:
        """Parse Markdown content into blocks."""
        blocks = []
        current_section = []
        
        lines = content.split('\n')
        current_text = []
        in_code_block = False
        code_content = []
        
        for line in lines:
            # Handle code blocks
            if line.startswith('```'):
                if in_code_block:
                    # End code block
                    blocks.append(ContentBlock(
                        content='\n'.join(code_content),
                        block_type="code",
                        section_hierarchy=current_section.copy()
                    ))
                    code_content = []
                    in_code_block = False
                else:
                    # Start code block - save accumulated text first
                    if current_text:
                        text = '\n'.join(current_text).strip()
                        if text:
                            blocks.append(ContentBlock(
                                content=text,
                                block_type="text",
                                section_hierarchy=current_section.copy()
                            ))
                        current_text = []
                    in_code_block = True
                continue
            
            if in_code_block:
                code_content.append(line)
                continue
            
            # Handle headings
            heading_match = re.match(r'^(#{1,6})\s+(.+)$', line)
            if heading_match:
                # Save accumulated text
                if current_text:
                    text = '\n'.join(current_text).strip()
                    if text:
                        blocks.append(ContentBlock(
                            content=text,
                            block_type="text",
                            section_hierarchy=current_section.copy()
                        ))
                    current_text = []
                
                level = len(heading_match.group(1)) - 1
                title = heading_match.group(2)
                current_section = current_section[:level] + [title]
                
                blocks.append(ContentBlock(
                    content=title,
                    block_type="heading",
                    section_hierarchy=current_section.copy(),
                    metadata={"level": level}
                ))
            else:
                current_text.append(line)
        
        # Process remaining text
        if current_text:
            text = '\n'.join(current_text).strip()
            if text:
                blocks.append(ContentBlock(
                    content=text,
                    block_type="text",
                    section_hierarchy=current_section.copy()
                ))
        
        return blocks
    
    def _extract_toc(self, content: str) -> List[Dict[str, Any]]:
        """Extract TOC from headings."""
        toc = []
        for match in re.finditer(r'^(#{1,6})\s+(.+)$', content, re.MULTILINE):
            toc.append({
                "title": match.group(2),
                "level": len(match.group(1)) - 1
            })
        return toc


class DocumentIngestionService:
    """
    Main service for document ingestion.
    
    Automatically selects the appropriate parser based on file type.
    """
    
    def __init__(self, settings=None):
        """
        Initialize the ingestion service.
        
        Args:
            settings: Optional IngestionSettings
        """
        self.settings = settings
        
        # Initialize parsers
        self.parsers: List[DocumentParser] = [
            PDFParser(
                extract_images=settings.extract_images if settings else False,
                ocr_enabled=settings.ocr_enabled if settings else False
            ),
            EPUBParser(),
            HTMLParser(),
            LaTeXParser(),
            MarkdownParser(),
        ]
    
    def ingest(self, file_path: Union[str, Path]) -> ParsedDocument:
        """
        Ingest a document from file.
        
        Args:
            file_path: Path to the document
            
        Returns:
            ParsedDocument with extracted content
            
        Raises:
            ValueError: If file format is not supported
            FileNotFoundError: If file doesn't exist
        """
        file_path = Path(file_path)
        
        if not file_path.exists():
            raise FileNotFoundError(f"Document not found: {file_path}")
        
        # Check file size
        if self.settings and self.settings.max_file_size_mb:
            file_size_mb = file_path.stat().st_size / (1024 * 1024)
            if file_size_mb > self.settings.max_file_size_mb:
                raise ValueError(
                    f"File size ({file_size_mb:.1f}MB) exceeds limit "
                    f"({self.settings.max_file_size_mb}MB)"
                )
        
        # Find appropriate parser
        for parser in self.parsers:
            if parser.supports(file_path):
                return parser.parse(file_path)
        
        raise ValueError(f"Unsupported file format: {file_path.suffix}")
    
    def ingest_text(self, text: str, title: Optional[str] = None) -> ParsedDocument:
        """
        Ingest raw text content.
        
        Args:
            text: Raw text content
            title: Optional document title
            
        Returns:
            ParsedDocument
        """
        metadata = DocumentMetadata(
            title=title,
            source_format=DocumentFormat.TEXT,
            word_count=len(text.split())
        )
        
        # Split into paragraphs
        paragraphs = re.split(r'\n\s*\n', text)
        content_blocks = [
            ContentBlock(content=p.strip(), block_type="text")
            for p in paragraphs if p.strip()
        ]
        
        return ParsedDocument(
            metadata=metadata,
            content_blocks=content_blocks,
            raw_text=text
        )
    
    def get_supported_formats(self) -> List[str]:
        """Get list of supported file extensions."""
        if self.settings:
            return self.settings.supported_formats
        return [".pdf", ".epub", ".html", ".htm", ".tex", ".latex", ".md", ".markdown"]
