"""Shared wikitext parser with caching to avoid duplicate parsing."""
from __future__ import annotations

import asyncio
import mwparserfromhell

PARSER_TIMEOUT = 60  # seconds


class WikitextParser:
    """Caches parsed wikicode to avoid re-parsing the same text."""

    CITE_TEMPLATES = {
        'cite web', 'cite book', 'cite journal', 'cite news',
        'cite magazine', 'cite conference', 'cite encyclopedia',
        'cite report', 'cite thesis', 'cite press release',
        'citation', 'sfn', 'harvnb', 'harv'
    }

    def __init__(self):
        self._cached_text: str | None = None
        self._cached_wikicode = None
        self.last_parse_timed_out: bool = False

    async def _parse_async(self, text: str):
        """Parse wikitext with timeout protection."""
        try:
            result = await asyncio.wait_for(
                asyncio.to_thread(mwparserfromhell.parse, text),
                timeout=PARSER_TIMEOUT
            )
            return result, False
        except TimeoutError:
            return None, True

    def _get_wikicode(self, text: str):
        """Parse text and cache result. Returns cached result if text unchanged."""
        if text != self._cached_text:
            self._cached_text = text
            self._cached_wikicode, self.last_parse_timed_out = asyncio.run(self._parse_async(text))
        return self._cached_wikicode

    def extract_external_links(self, text: str | None) -> list[str] | None:
        """Extract all external link URLs from wikitext."""
        if text is None:
            return None
        try:
            wikicode = self._get_wikicode(text)
            return [str(link.url) for link in wikicode.filter_external_links()]
        except Exception:
            return None

    def extract_citations(self, text: str | None) -> list[str] | None:
        """Extract citations from ref tags and cite templates."""
        if text is None:
            return None
        try:
            wikicode = self._get_wikicode(text)
            citations = []

            # Extract ref tag contents
            for tag in wikicode.filter_tags():
                if tag.tag.lower() == 'ref':
                    content = str(tag.contents).strip()
                    if content:
                        citations.append(f"ref:{content}")

            # Extract cite templates
            for template in wikicode.filter_templates():
                template_name = str(template.name).strip().lower()
                if any(template_name.startswith(cite) for cite in self.CITE_TEMPLATES):
                    citations.append(f"template:{str(template)}")

            return citations
        except Exception:
            return None

    def extract_wikilinks(self, text: str | None) -> list[dict[str, str | None]] | None:
        """Extract all internal wikilinks with title and display text."""
        if text is None:
            return None
        try:
            wikicode = self._get_wikicode(text)
            result = []
            for link in wikicode.filter_wikilinks():
                title = str(link.title).strip()
                # text is None if no pipe, otherwise the display text
                display_text = str(link.text).strip() if link.text else None
                result.append({"title": title, "text": display_text})
            return result
        except Exception:
            return None

    def extract_templates(self, text: str | None) -> list[dict] | None:
        """Extract all templates with their names and parameters."""
        if text is None:
            return None
        try:
            wikicode = self._get_wikicode(text)
            result = []
            for template in wikicode.filter_templates():
                name = str(template.name).strip()
                params = {}
                for param in template.params:
                    param_name = str(param.name).strip()
                    param_value = str(param.value).strip()
                    params[param_name] = param_value
                result.append({"name": name, "params": params})
            return result
        except Exception:
            return None

    def extract_headings(self, text: str | None) -> list[dict] | None:
        """Extract all section headings with their levels."""
        if text is None:
            return None
        try:
            wikicode = self._get_wikicode(text)
            result = []
            for heading in wikicode.filter_headings():
                level = heading.level
                heading_text = str(heading.title).strip()
                result.append({"level": level, "text": heading_text})
            return result
        except Exception:
            return None
