"""
EPUB parser — uses ebooklib + BeautifulSoup4 for content extraction.

Iterates spine in reading order. Each spine item (HTML document) is parsed
with BeautifulSoup. Chapter boundaries are detected from the spine order
and the document title.
"""

from __future__ import annotations

import re
from collections.abc import Iterator

from .base import BaseParser, FileRecord, RawElement

_ADMONITION_CLASSES = re.compile(
    r"\b(note|warning|tip|caution|important|danger|alert|callout)\b", re.I
)


def _parse_html_content(html_content: bytes, chapter_num: int) -> Iterator[RawElement]:
    """Parse HTML content from an EPUB spine item."""
    try:
        from bs4 import BeautifulSoup
    except ImportError as e:
        raise ImportError("beautifulsoup4 required: pip install beautifulsoup4") from e

    soup = BeautifulSoup(html_content, "html.parser")

    # Remove boilerplate tags
    for tag in soup.find_all(["script", "style", "nav"]):
        tag.decompose()

    HEADING_TAGS = ["h1", "h2", "h3", "h4", "h5", "h6"]
    SEEN_TAGS: set[int] = set()  # track visited element ids to avoid double-emit

    def process_element(el) -> Iterator[RawElement]:  # type: ignore[no-untyped-def]
        if id(el) in SEEN_TAGS:
            return
        SEEN_TAGS.add(id(el))

        tag = el.name if hasattr(el, "name") else None
        if not tag:
            return

        if tag in HEADING_TAGS:
            text = el.get_text(separator=" ").strip()
            if text:
                level = int(tag[1])
                yield RawElement(
                    element_type="structural:section_header" if level > 1 else "structural:title",
                    text=text,
                    heading_level=min(level, 4),
                    detection_tier=1,
                    extra={"extraction_method": "epub:bs4-heading", "confidence": 0.97},
                )

        elif tag == "p":
            text = el.get_text(separator=" ").strip()
            if not text:
                return

            class_attr = " ".join(el.get("class", []))
            adm = _ADMONITION_CLASSES.search(class_attr)
            if adm:
                yield RawElement(
                    element_type="text:admonition",
                    text=text,
                    admonition_data={"kind": adm.group(1).lower(), "title": None},
                    extra={"extraction_method": "epub:bs4", "confidence": 0.85},
                )
            else:
                yield RawElement(
                    element_type="text:narrative",
                    text=text,
                    extra={"extraction_method": "epub:bs4", "confidence": 0.90},
                )

        elif tag in ("ul", "ol"):
            is_ordered = tag == "ol"
            for idx, li in enumerate(el.find_all("li", recursive=False)):
                li_text = li.get_text(separator=" ").strip()
                if li_text:
                    yield RawElement(
                        element_type="list:item_ordered" if is_ordered else "list:item",
                        text=li_text,
                        list_data={
                            "list_index": idx,
                            "list_depth": 0,
                            "first_item_sequence_index": 0,
                        },
                        extra={"extraction_method": "epub:bs4-list", "confidence": 0.90},
                    )

        elif tag == "table":
            yield from _parse_table(el)

        elif tag == "pre":
            code_el = el.find("code")
            code_text = (code_el or el).get_text().strip()
            if code_text:
                lang_class = (
                    (code_el or el).get("class", []) if (code_el or el).get("class") else []
                )
                lang = None
                for c in lang_class if isinstance(lang_class, list) else [lang_class]:
                    m = re.search(r"language-(\w+)", str(c))
                    if m:
                        lang = m.group(1)
                        break
                yield RawElement(
                    element_type="code:block",
                    text=code_text,
                    extra={
                        "extraction_method": "epub:bs4-code",
                        "confidence": 0.98,
                        "language": lang,
                    },
                )

        elif tag == "blockquote":
            text = el.get_text(separator=" ").strip()
            if text:
                yield RawElement(
                    element_type="text:pull_quote",
                    text=text,
                    extra={"extraction_method": "epub:bs4", "confidence": 0.85},
                )

    def _parse_table(table_el) -> Iterator[RawElement]:  # type: ignore[no-untyped-def]
        headers: list[str] = []
        rows: list[list[str]] = []
        has_header = False

        thead = table_el.find("thead")
        if thead:
            for tr in thead.find_all("tr"):
                headers = [th.get_text(strip=True) for th in tr.find_all(["th", "td"])]
                has_header = bool(headers)
                break

        tbody = table_el.find("tbody") or table_el
        for tr in tbody.find_all("tr"):
            cells = [td.get_text(strip=True) for td in tr.find_all(["td", "th"])]
            if cells:
                rows.append(cells)

        if headers:
            sep = "|" + "|".join("---" for _ in headers) + "|"
            md_lines = ["|" + "|".join(headers) + "|", sep] + [
                "|" + "|".join(row) + "|" for row in rows
            ]
        else:
            md_lines = ["|" + "|".join(row) + "|" for row in rows]

        markdown = "\n".join(md_lines)
        if markdown:
            yield RawElement(
                element_type="table:simple",
                text=markdown,
                table_data={
                    "markdown": markdown,
                    "structured": {"headers": headers, "rows": rows},
                    "has_header_row": has_header,
                    "row_count": len(rows),
                    "header_row_count": 1 if has_header else 0,
                    "col_count": len(headers) if headers else (len(rows[0]) if rows else 0),
                },
                extra={"extraction_method": "epub:bs4-table", "confidence": 0.88},
            )

    body = soup.find("body") or soup
    for el in body.children:
        if hasattr(el, "name") and el.name:
            yield from process_element(el)


class EPUBParser(BaseParser):
    handles = [("application/epub+zip", None)]

    def parse(self, file_record: FileRecord) -> Iterator[RawElement]:
        try:
            import ebooklib  # type: ignore[import-untyped]
            from ebooklib import epub as ebooklib_epub
        except ImportError as e:
            raise ImportError("ebooklib required: pip install ebooklib") from e

        import warnings

        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            book = ebooklib_epub.read_epub(str(file_record.path))

        for chapter_num, item in enumerate(book.get_items_of_type(ebooklib.ITEM_DOCUMENT), start=1):
            content = item.get_content()
            if content:
                yield from _parse_html_content(content, chapter_num)
