"""Citation formatter for adding hyperlinks and alternative citation styles."""

import re
from enum import Enum
from typing import Dict, Tuple
from urllib.parse import urlparse


class CitationMode(Enum):
    """Available citation formatting modes."""

    NUMBER_HYPERLINKS = "number_hyperlinks"  # [1] with hyperlinks
    DOMAIN_HYPERLINKS = "domain_hyperlinks"  # [arxiv.org] with hyperlinks
    DOMAIN_ID_HYPERLINKS = (
        "domain_id_hyperlinks"  # [arxiv.org] or [arxiv.org-1] with smart IDs
    )
    DOMAIN_ID_ALWAYS_HYPERLINKS = (
        "domain_id_always_hyperlinks"  # [arxiv.org-1] always with IDs
    )
    NO_HYPERLINKS = "no_hyperlinks"  # [1] without hyperlinks


class CitationFormatter:
    """Formats citations in markdown documents with various styles."""

    def __init__(self, mode: CitationMode = CitationMode.NUMBER_HYPERLINKS):
        self.mode = mode
        # Use negative lookbehind and lookahead to avoid matching already formatted citations
        self.citation_pattern = re.compile(r"(?<!\[)\[(\d+)\](?!\])")
        self.comma_citation_pattern = re.compile(r"\[(\d+(?:,\s*\d+)+)\]")
        # Also match "Source X" or "source X" patterns
        self.source_word_pattern = re.compile(r"\b[Ss]ource\s+(\d+)\b")
        self.sources_pattern = re.compile(
            r"^\[(\d+(?:,\s*\d+)*)\]\s*(.+?)(?:\n\s*URL:\s*(.+?))?$",
            re.MULTILINE,
        )

    def _create_source_word_replacer(self, formatter_func):
        """Create a replacement function for 'Source X' patterns.

        Args:
            formatter_func: A function that takes citation_num and returns formatted text

        Returns:
            A replacement function for use with regex sub
        """

        def replace_source_word(match):
            citation_num = match.group(1)
            return formatter_func(citation_num)

        return replace_source_word

    def _create_citation_formatter(self, sources_dict, format_pattern):
        """Create a formatter function for citations.

        Args:
            sources_dict: Dictionary mapping citation numbers to data
            format_pattern: A callable that takes (citation_num, data) and returns formatted string

        Returns:
            A function that formats citations or returns fallback
        """

        def formatter(citation_num):
            if citation_num in sources_dict:
                data = sources_dict[citation_num]
                return format_pattern(citation_num, data)
            return f"[{citation_num}]"

        return formatter

    def format_document(self, content: str) -> str:
        """
        Format citations in the document according to the selected mode.

        Args:
            content: The markdown content to format

        Returns:
            Formatted markdown content
        """
        if self.mode == CitationMode.NO_HYPERLINKS:
            return content

        # Extract sources section
        sources_start = self._find_sources_section(content)
        if sources_start == -1:
            return content

        document_content = content[:sources_start]
        sources_content = content[sources_start:]

        # Parse sources
        sources = self._parse_sources(sources_content)

        # Format citations in document
        if self.mode == CitationMode.NUMBER_HYPERLINKS:
            formatted_content = self._format_number_hyperlinks(
                document_content, sources
            )
        elif self.mode == CitationMode.DOMAIN_HYPERLINKS:
            formatted_content = self._format_domain_hyperlinks(
                document_content, sources
            )
        elif self.mode == CitationMode.DOMAIN_ID_HYPERLINKS:
            formatted_content = self._format_domain_id_hyperlinks(
                document_content, sources
            )
        elif self.mode == CitationMode.DOMAIN_ID_ALWAYS_HYPERLINKS:
            formatted_content = self._format_domain_id_always_hyperlinks(
                document_content, sources
            )
        else:
            formatted_content = document_content

        # Rebuild document
        return formatted_content + sources_content

    def _find_sources_section(self, content: str) -> int:
        """Find the start of the sources/references section."""
        patterns = [
            r"^#{1,3}\s*(?:Sources|References|Bibliography|Citations)",
            r"^(?:Sources|References|Bibliography|Citations):?\s*$",
        ]

        for pattern in patterns:
            match = re.search(pattern, content, re.MULTILINE | re.IGNORECASE)
            if match:
                return match.start()

        return -1

    def _parse_sources(
        self, sources_content: str
    ) -> Dict[str, Tuple[str, str]]:
        """
        Parse sources section to extract citation numbers, titles, and URLs.

        Returns:
            Dictionary mapping citation number to (title, url) tuple
        """
        sources = {}
        matches = list(self.sources_pattern.finditer(sources_content))

        for match in matches:
            citation_nums_str = match.group(1)
            title = match.group(2).strip()
            url = match.group(3).strip() if match.group(3) else ""

            # Handle comma-separated citation numbers like [36, 3]
            # Split by comma and strip whitespace
            individual_nums = [
                num.strip() for num in citation_nums_str.split(",")
            ]

            # Add an entry for each individual number
            for num in individual_nums:
                sources[num] = (title, url)

        return sources

    def _format_number_hyperlinks(
        self, content: str, sources: Dict[str, Tuple[str, str]]
    ) -> str:
        """Replace [1] with hyperlinked version where only the number is linked."""

        # First handle comma-separated citations like [1, 2, 3]
        def replace_comma_citations(match):
            citation_nums = match.group(1)
            # Split by comma and strip whitespace
            nums = [num.strip() for num in citation_nums.split(",")]
            formatted_citations = []

            for num in nums:
                if num in sources and sources[num][1]:
                    url = sources[num][1]
                    formatted_citations.append(f"[[{num}]]({url})")
                else:
                    formatted_citations.append(f"[{num}]")

            return "".join(formatted_citations)

        content = self.comma_citation_pattern.sub(
            replace_comma_citations, content
        )

        # Filter sources that have URLs
        url_sources = {
            num: (title, url) for num, (title, url) in sources.items() if url
        }

        # Create formatter for citations with number hyperlinks
        def format_number_link(citation_num, data):
            _, url = data
            return f"[[{citation_num}]]({url})"

        formatter = self._create_citation_formatter(
            url_sources, format_number_link
        )

        # Handle individual citations
        def replace_citation(match):
            return (
                formatter(match.group(1))
                if match.group(1) in url_sources
                else match.group(0)
            )

        content = self.citation_pattern.sub(replace_citation, content)

        # Also handle "Source X" patterns
        return self.source_word_pattern.sub(
            self._create_source_word_replacer(formatter), content
        )

    def _format_domain_hyperlinks(
        self, content: str, sources: Dict[str, Tuple[str, str]]
    ) -> str:
        """Replace [1] with [domain.com] hyperlinked version."""

        # First handle comma-separated citations like [1, 2, 3]
        def replace_comma_citations(match):
            citation_nums = match.group(1)
            # Split by comma and strip whitespace
            nums = [num.strip() for num in citation_nums.split(",")]
            formatted_citations = []

            for num in nums:
                if num in sources and sources[num][1]:
                    url = sources[num][1]
                    domain = self._extract_domain(url)
                    formatted_citations.append(f"[[{domain}]]({url})")
                else:
                    formatted_citations.append(f"[{num}]")

            return "".join(formatted_citations)

        content = self.comma_citation_pattern.sub(
            replace_comma_citations, content
        )

        # Filter sources that have URLs
        url_sources = {
            num: (title, url) for num, (title, url) in sources.items() if url
        }

        # Create formatter for citations with domain hyperlinks
        def format_domain_link(citation_num, data):
            _, url = data
            domain = self._extract_domain(url)
            return f"[[{domain}]]({url})"

        formatter = self._create_citation_formatter(
            url_sources, format_domain_link
        )

        # Handle individual citations
        def replace_citation(match):
            return (
                formatter(match.group(1))
                if match.group(1) in url_sources
                else match.group(0)
            )

        content = self.citation_pattern.sub(replace_citation, content)

        # Also handle "Source X" patterns
        return self.source_word_pattern.sub(
            self._create_source_word_replacer(formatter), content
        )

    def _format_domain_id_hyperlinks(
        self, content: str, sources: Dict[str, Tuple[str, str]]
    ) -> str:
        """Replace [1] with [domain.com-1] hyperlinked version with hyphen-separated IDs."""
        # First, create a mapping of domains to their citation numbers
        domain_citations = {}

        for citation_num, (title, url) in sources.items():
            if url:
                domain = self._extract_domain(url)
                if domain not in domain_citations:
                    domain_citations[domain] = []
                domain_citations[domain].append((citation_num, url))

        # Create a mapping from citation number to domain with ID
        citation_to_domain_id = {}
        for domain, citations in domain_citations.items():
            if len(citations) > 1:
                # Multiple citations from same domain - add hyphen and number
                for idx, (citation_num, url) in enumerate(citations, 1):
                    citation_to_domain_id[citation_num] = (
                        f"{domain}-{idx}",
                        url,
                    )
            else:
                # Single citation from domain - no ID needed
                citation_num, url = citations[0]
                citation_to_domain_id[citation_num] = (domain, url)

        # First handle comma-separated citations
        def replace_comma_citations(match):
            citation_nums = match.group(1)
            nums = [num.strip() for num in citation_nums.split(",")]
            formatted_citations = []

            for num in nums:
                if num in citation_to_domain_id:
                    domain_id, url = citation_to_domain_id[num]
                    formatted_citations.append(f"[[{domain_id}]]({url})")
                else:
                    formatted_citations.append(f"[{num}]")

            return "".join(formatted_citations)

        content = self.comma_citation_pattern.sub(
            replace_comma_citations, content
        )

        # Create formatter for citations with domain_id hyperlinks
        def format_domain_id_link(citation_num, data):
            domain_id, url = data
            return f"[[{domain_id}]]({url})"

        formatter = self._create_citation_formatter(
            citation_to_domain_id, format_domain_id_link
        )

        # Handle individual citations
        def replace_citation(match):
            return (
                formatter(match.group(1))
                if match.group(1) in citation_to_domain_id
                else match.group(0)
            )

        content = self.citation_pattern.sub(replace_citation, content)

        # Also handle "Source X" patterns
        return self.source_word_pattern.sub(
            self._create_source_word_replacer(formatter), content
        )

    def _format_domain_id_always_hyperlinks(
        self, content: str, sources: Dict[str, Tuple[str, str]]
    ) -> str:
        """Replace [1] with [domain.com-1] hyperlinked version, always with IDs."""
        # First, create a mapping of domains to their citation numbers
        domain_citations = {}

        for citation_num, (title, url) in sources.items():
            if url:
                domain = self._extract_domain(url)
                if domain not in domain_citations:
                    domain_citations[domain] = []
                domain_citations[domain].append((citation_num, url))

        # Create a mapping from citation number to domain with ID
        citation_to_domain_id = {}
        for domain, citations in domain_citations.items():
            # Always add hyphen and number for consistency
            for idx, (citation_num, url) in enumerate(citations, 1):
                citation_to_domain_id[citation_num] = (f"{domain}-{idx}", url)

        # First handle comma-separated citations
        def replace_comma_citations(match):
            citation_nums = match.group(1)
            nums = [num.strip() for num in citation_nums.split(",")]
            formatted_citations = []

            for num in nums:
                if num in citation_to_domain_id:
                    domain_id, url = citation_to_domain_id[num]
                    formatted_citations.append(f"[[{domain_id}]]({url})")
                else:
                    formatted_citations.append(f"[{num}]")

            return "".join(formatted_citations)

        content = self.comma_citation_pattern.sub(
            replace_comma_citations, content
        )

        # Create formatter for citations with domain_id hyperlinks
        def format_domain_id_link(citation_num, data):
            domain_id, url = data
            return f"[[{domain_id}]]({url})"

        formatter = self._create_citation_formatter(
            citation_to_domain_id, format_domain_id_link
        )

        # Handle individual citations
        def replace_citation(match):
            return (
                formatter(match.group(1))
                if match.group(1) in citation_to_domain_id
                else match.group(0)
            )

        content = self.citation_pattern.sub(replace_citation, content)

        # Also handle "Source X" patterns
        return self.source_word_pattern.sub(
            self._create_source_word_replacer(formatter), content
        )

    def _to_superscript(self, text: str) -> str:
        """Convert text to Unicode superscript."""
        superscript_map = {
            "0": "⁰",
            "1": "¹",
            "2": "²",
            "3": "³",
            "4": "⁴",
            "5": "⁵",
            "6": "⁶",
            "7": "⁷",
            "8": "⁸",
            "9": "⁹",
        }
        return "".join(superscript_map.get(c, c) for c in text)

    def _extract_domain(self, url: str) -> str:
        """Extract domain name from URL."""
        try:
            parsed = urlparse(url)
            domain = parsed.netloc
            # Remove www. prefix if present
            if domain.startswith("www."):
                domain = domain[4:]
            # Keep known domains as-is
            known_domains = {
                "arxiv.org": "arxiv.org",
                "github.com": "github.com",
                "reddit.com": "reddit.com",
                "youtube.com": "youtube.com",
                "pypi.org": "pypi.org",
                "milvus.io": "milvus.io",
                "medium.com": "medium.com",
            }

            for known, display in known_domains.items():
                if known in domain:
                    return display

            # For other domains, extract main domain
            parts = domain.split(".")
            if len(parts) >= 2:
                return ".".join(parts[-2:])
            return domain
        except:
            return "source"


class QuartoExporter:
    """Export markdown documents to Quarto (.qmd) format."""

    def __init__(self):
        self.citation_pattern = re.compile(r"(?<!\[)\[(\d+)\](?!\])")
        self.comma_citation_pattern = re.compile(r"\[(\d+(?:,\s*\d+)+)\]")

    def export_to_quarto(self, content: str, title: str = None) -> str:
        """
        Convert markdown document to Quarto format.

        Args:
            content: Markdown content
            title: Document title (if None, will extract from content)

        Returns:
            Quarto formatted content
        """
        # Extract title from markdown if not provided
        if not title:
            title_match = re.search(r"^#\s+(.+)$", content, re.MULTILINE)
            title = title_match.group(1) if title_match else "Research Report"

        # Create Quarto YAML header
        from datetime import datetime, UTC

        current_date = datetime.now(UTC).strftime("%Y-%m-%d")
        yaml_header = f"""---
title: "{title}"
author: "Local Deep Research"
date: "{current_date}"
format:
  html:
    toc: true
    toc-depth: 3
    number-sections: true
  pdf:
    toc: true
    number-sections: true
    colorlinks: true
bibliography: references.bib
csl: apa.csl
---

"""

        # Process content
        processed_content = content

        # First handle comma-separated citations like [1, 2, 3]
        def replace_comma_citations(match):
            citation_nums = match.group(1)
            # Split by comma and strip whitespace
            nums = [num.strip() for num in citation_nums.split(",")]
            refs = [f"@ref{num}" for num in nums]
            return f"[{', '.join(refs)}]"

        processed_content = self.comma_citation_pattern.sub(
            replace_comma_citations, processed_content
        )

        # Then convert individual citations to Quarto format [@citation]
        def replace_citation(match):
            citation_num = match.group(1)
            return f"[@ref{citation_num}]"

        processed_content = self.citation_pattern.sub(
            replace_citation, processed_content
        )

        # Generate bibliography file content
        bib_content = self._generate_bibliography(content)

        # Add note about bibliography file
        bibliography_note = (
            "\n\n::: {.callout-note}\n## Bibliography File Required\n\nThis document requires a `references.bib` file in the same directory with the following content:\n\n```bibtex\n"
            + bib_content
            + "\n```\n:::\n"
        )

        return yaml_header + processed_content + bibliography_note

    def _generate_bibliography(self, content: str) -> str:
        """Generate BibTeX bibliography from sources."""
        sources_pattern = re.compile(
            r"^\[(\d+)\]\s*(.+?)(?:\n\s*URL:\s*(.+?))?$", re.MULTILINE
        )

        bibliography = ""
        matches = list(sources_pattern.finditer(content))

        for match in matches:
            citation_num = match.group(1)
            title = match.group(2).strip()
            url = match.group(3).strip() if match.group(3) else ""

            # Generate BibTeX entry
            bib_entry = f"@misc{{ref{citation_num},\n"
            bib_entry += f'  title = "{{{title}}}",\n'
            if url:
                bib_entry += f"  url = {{{url}}},\n"
                bib_entry += f'  howpublished = "\\url{{{url}}}",\n'
            bib_entry += f"  year = {{{2024}}},\n"
            bib_entry += '  note = "Accessed: \\today"\n'
            bib_entry += "}\n"

            bibliography += bib_entry + "\n"

        return bibliography.strip()


class RISExporter:
    """Export references to RIS format for reference managers like Zotero."""

    def __init__(self):
        self.sources_pattern = re.compile(
            r"^\[(\d+(?:,\s*\d+)*)\]\s*(.+?)(?:\n\s*URL:\s*(.+?))?$",
            re.MULTILINE,
        )

    def export_to_ris(self, content: str) -> str:
        """
        Extract references from markdown and convert to RIS format.

        Args:
            content: Markdown content with sources

        Returns:
            RIS formatted references
        """
        # Find sources section
        sources_start = content.find("## Sources")
        if sources_start == -1:
            sources_start = content.find("## References")
        if sources_start == -1:
            sources_start = content.find("### Sources")
        if sources_start == -1:
            sources_start = content.find("### SOURCES")

        if sources_start == -1:
            return ""

        # Find the end of the first sources section (before any other major section)
        sources_content = content[sources_start:]

        # Look for the next major section to avoid duplicates
        next_section_markers = [
            "\n## ALL SOURCES",
            "\n### ALL SOURCES",
            "\n## Research Metrics",
            "\n### Research Metrics",
            "\n## SEARCH QUESTIONS",
            "\n### SEARCH QUESTIONS",
            "\n## DETAILED FINDINGS",
            "\n### DETAILED FINDINGS",
            "\n---",  # Horizontal rule often separates sections
        ]

        sources_end = len(sources_content)
        for marker in next_section_markers:
            pos = sources_content.find(marker)
            if pos != -1 and pos < sources_end:
                sources_end = pos

        sources_content = sources_content[:sources_end]

        # Parse sources and generate RIS entries
        ris_entries = []
        seen_refs = set()  # Track which references we've already processed

        # Split sources into individual entries
        import re

        # Pattern to match each source entry
        source_entry_pattern = re.compile(
            r"^\[(\d+)\]\s*(.+?)(?=^\[\d+\]|\Z)", re.MULTILINE | re.DOTALL
        )

        for match in source_entry_pattern.finditer(sources_content):
            citation_num = match.group(1)
            entry_text = match.group(2).strip()

            # Extract the title (first line)
            lines = entry_text.split("\n")
            title = lines[0].strip()

            # Extract URL, DOI, and other metadata from subsequent lines
            url = ""
            metadata = {}
            for line in lines[1:]:
                line = line.strip()
                if line.startswith("URL:"):
                    url = line[4:].strip()
                elif line.startswith("DOI:"):
                    metadata["doi"] = line[4:].strip()
                elif line.startswith("Published in"):
                    metadata["journal"] = line[12:].strip()
                # Add more metadata parsing as needed
                elif line:
                    # Store other lines as additional metadata
                    if "additional" not in metadata:
                        metadata["additional"] = []
                    metadata["additional"].append(line)

            # Combine title with additional metadata lines for full context
            full_text = entry_text

            # Create a unique key to avoid duplicates
            ref_key = (citation_num, title, url)
            if ref_key not in seen_refs:
                seen_refs.add(ref_key)
                # Create RIS entry with full text for metadata extraction
                ris_entry = self._create_ris_entry(
                    citation_num, full_text, url, metadata
                )
                ris_entries.append(ris_entry)

        return "\n".join(ris_entries)

    def _create_ris_entry(
        self, ref_id: str, full_text: str, url: str = "", metadata: dict = None
    ) -> str:
        """Create a single RIS entry."""
        lines = []

        # Parse metadata from full text
        import re

        if metadata is None:
            metadata = {}

        # Extract title from first line
        lines = full_text.split("\n")
        title = lines[0].strip()

        # Extract year from full text (looks for 4-digit year)
        year_match = re.search(r"\b(19\d{2}|20\d{2})\b", full_text)
        year = year_match.group(1) if year_match else None

        # Extract authors if present (looks for "by Author1, Author2")
        authors_match = re.search(
            r"\bby\s+([^.\n]+?)(?:\.|\n|$)", full_text, re.IGNORECASE
        )
        authors = []
        if authors_match:
            authors_text = authors_match.group(1)
            # Split by 'and' or ','
            author_parts = re.split(r"\s*(?:,|\sand\s|&)\s*", authors_text)
            authors = [a.strip() for a in author_parts if a.strip()]

        # Extract DOI from metadata or text
        doi = metadata.get("doi")
        if not doi:
            doi_match = re.search(
                r"DOI:\s*([^\s\n]+)", full_text, re.IGNORECASE
            )
            doi = doi_match.group(1) if doi_match else None

        # Clean title - remove author and metadata info for cleaner title
        clean_title = title
        if authors_match and authors_match.start() < len(title):
            clean_title = (
                title[: authors_match.start()] + title[authors_match.end() :]
                if authors_match.end() < len(title)
                else title[: authors_match.start()]
            )
        clean_title = re.sub(
            r"\s*DOI:\s*[^\s]+", "", clean_title, flags=re.IGNORECASE
        )
        clean_title = re.sub(
            r"\s*Published in.*", "", clean_title, flags=re.IGNORECASE
        )
        clean_title = re.sub(
            r"\s*Volume.*", "", clean_title, flags=re.IGNORECASE
        )
        clean_title = re.sub(
            r"\s*Pages.*", "", clean_title, flags=re.IGNORECASE
        )
        clean_title = clean_title.strip()

        # TY - Type of reference (ELEC for electronic source/website)
        lines.append("TY  - ELEC")

        # ID - Reference ID
        lines.append(f"ID  - ref{ref_id}")

        # TI - Title
        lines.append(f"TI  - {clean_title if clean_title else title}")

        # AU - Authors
        for author in authors:
            lines.append(f"AU  - {author}")

        # DO - DOI
        if doi:
            lines.append(f"DO  - {doi}")

        # PY - Publication year (if found in title)
        if year:
            lines.append(f"PY  - {year}")

        # UR - URL
        if url:
            lines.append(f"UR  - {url}")

            # Try to extract domain as publisher
            try:
                from urllib.parse import urlparse

                parsed = urlparse(url)
                domain = parsed.netloc
                if domain.startswith("www."):
                    domain = domain[4:]
                # Extract readable publisher name from domain
                if domain == "github.com" or domain.endswith(".github.com"):
                    lines.append("PB  - GitHub")
                elif domain == "arxiv.org" or domain.endswith(".arxiv.org"):
                    lines.append("PB  - arXiv")
                elif domain == "reddit.com" or domain.endswith(".reddit.com"):
                    lines.append("PB  - Reddit")
                elif (
                    domain == "youtube.com"
                    or domain == "m.youtube.com"
                    or domain.endswith(".youtube.com")
                ):
                    lines.append("PB  - YouTube")
                elif domain == "medium.com" or domain.endswith(".medium.com"):
                    lines.append("PB  - Medium")
                elif domain == "pypi.org" or domain.endswith(".pypi.org"):
                    lines.append("PB  - Python Package Index (PyPI)")
                else:
                    # Use domain as publisher
                    lines.append(f"PB  - {domain}")
            except:
                pass

        # Y1 - Year accessed (current year)
        from datetime import datetime, UTC

        current_year = datetime.now(UTC).year
        lines.append(f"Y1  - {current_year}")

        # DA - Date accessed
        current_date = datetime.now(UTC).strftime("%Y/%m/%d")
        lines.append(f"DA  - {current_date}")

        # LA - Language
        lines.append("LA  - en")

        # ER - End of reference
        lines.append("ER  - ")

        return "\n".join(lines)


class LaTeXExporter:
    """Export markdown documents to LaTeX format."""

    def __init__(self):
        self.citation_pattern = re.compile(r"\[(\d+)\]")
        self.heading_patterns = [
            (re.compile(r"^# (.+)$", re.MULTILINE), r"\\section{\1}"),
            (re.compile(r"^## (.+)$", re.MULTILINE), r"\\subsection{\1}"),
            (re.compile(r"^### (.+)$", re.MULTILINE), r"\\subsubsection{\1}"),
        ]
        self.emphasis_patterns = [
            (re.compile(r"\*\*(.+?)\*\*"), r"\\textbf{\1}"),
            (re.compile(r"\*(.+?)\*"), r"\\textit{\1}"),
            (re.compile(r"`(.+?)`"), r"\\texttt{\1}"),
        ]

    def export_to_latex(self, content: str) -> str:
        """
        Convert markdown document to LaTeX format.

        Args:
            content: Markdown content

        Returns:
            LaTeX formatted content
        """
        latex_content = self._create_latex_header()

        # Convert markdown to LaTeX
        body_content = content

        # Escape special LaTeX characters but preserve math mode
        # Split by $ to preserve math sections
        parts = body_content.split("$")
        for i in range(len(parts)):
            # Even indices are outside math mode
            if i % 2 == 0:
                # Only escape if not inside $$
                if not (
                    i > 0
                    and parts[i - 1] == ""
                    and i < len(parts) - 1
                    and parts[i + 1] == ""
                ):
                    # Preserve certain patterns that will be processed later
                    # like headings (#), emphasis (*), and citations ([n])
                    lines = parts[i].split("\n")
                    for j, line in enumerate(lines):
                        # Don't escape lines that start with # (headings)
                        if not line.strip().startswith("#"):
                            # Don't escape emphasis markers or citations for now
                            # They'll be handled by their own patterns
                            temp_line = line
                            # Escape special chars except *, #, [, ]
                            temp_line = temp_line.replace("&", r"\&")
                            temp_line = temp_line.replace("%", r"\%")
                            temp_line = temp_line.replace("_", r"\_")
                            # Don't escape { } inside citations
                            lines[j] = temp_line
                    parts[i] = "\n".join(lines)
        body_content = "$".join(parts)

        # Convert headings
        for pattern, replacement in self.heading_patterns:
            body_content = pattern.sub(replacement, body_content)

        # Convert emphasis
        for pattern, replacement in self.emphasis_patterns:
            body_content = pattern.sub(replacement, body_content)

        # Convert citations to LaTeX \cite{} format
        body_content = self.citation_pattern.sub(r"\\cite{\1}", body_content)

        # Convert lists
        body_content = self._convert_lists(body_content)

        # Add body content
        latex_content += body_content

        # Add bibliography section
        latex_content += self._create_bibliography(content)

        # Add footer
        latex_content += self._create_latex_footer()

        return latex_content

    def _create_latex_header(self) -> str:
        """Create LaTeX document header."""
        return r"""\documentclass[12pt]{article}
\usepackage[utf8]{inputenc}
\usepackage{hyperref}
\usepackage{cite}
\usepackage{url}

\title{Research Report}
\date{\today}

\begin{document}
\maketitle

"""

    def _create_latex_footer(self) -> str:
        """Create LaTeX document footer."""
        return "\n\\end{document}\n"

    def _escape_latex(self, text: str) -> str:
        """Escape special LaTeX characters in text."""
        # Escape special LaTeX characters
        replacements = [
            ("\\", r"\textbackslash{}"),  # Must be first
            ("&", r"\&"),
            ("%", r"\%"),
            ("$", r"\$"),
            ("#", r"\#"),
            ("_", r"\_"),
            ("{", r"\{"),
            ("}", r"\}"),
            ("~", r"\textasciitilde{}"),
            ("^", r"\textasciicircum{}"),
        ]

        for old, new in replacements:
            text = text.replace(old, new)

        return text

    def _convert_lists(self, content: str) -> str:
        """Convert markdown lists to LaTeX format."""
        # Simple conversion for bullet points
        content = re.sub(r"^- (.+)$", r"\\item \1", content, flags=re.MULTILINE)

        # Add itemize environment around list items
        lines = content.split("\n")
        result = []
        in_list = False

        for line in lines:
            if line.strip().startswith("\\item"):
                if not in_list:
                    result.append("\\begin{itemize}")
                    in_list = True
                result.append(line)
            else:
                if in_list and line.strip():
                    result.append("\\end{itemize}")
                    in_list = False
                result.append(line)

        if in_list:
            result.append("\\end{itemize}")

        return "\n".join(result)

    def _create_bibliography(self, content: str) -> str:
        """Extract sources and create LaTeX bibliography."""
        sources_start = content.find("## Sources")
        if sources_start == -1:
            sources_start = content.find("## References")

        if sources_start == -1:
            return ""

        sources_content = content[sources_start:]
        pattern = re.compile(
            r"^\[(\d+)\]\s*(.+?)(?:\n\s*URL:\s*(.+?))?$", re.MULTILINE
        )

        bibliography = "\n\\begin{thebibliography}{99}\n"

        for match in pattern.finditer(sources_content):
            citation_num = match.group(1)
            title = match.group(2).strip()
            url = match.group(3).strip() if match.group(3) else ""

            # Escape special LaTeX characters in title
            escaped_title = self._escape_latex(title)

            if url:
                bibliography += f"\\bibitem{{{citation_num}}} {escaped_title}. \\url{{{url}}}\n"
            else:
                bibliography += (
                    f"\\bibitem{{{citation_num}}} {escaped_title}.\n"
                )

        bibliography += "\\end{thebibliography}\n"

        return bibliography
