from __future__ import annotations

"""Lightweight HTML parser for web documents.

This parser uses the Python standard library :mod:`html.parser` to extract
headings (``<h1>``-``<h6>``) and paragraph (``<p>``) blocks from HTML.

It is intentionally simple but gives us a more structured ``DocumentTree`` for
web pages than treating everything as plain text.
"""

from html.parser import HTMLParser
from typing import List, Tuple

from ..core.models import DocumentNode, DocumentTree, NodeType, RawDocument, SourceType
from .base import BaseParser


_Block = Tuple[str, int | None, str]


class _HTMLBlockExtractor(HTMLParser):
    """Extract heading/paragraph text blocks from HTML.

    The resulting ``blocks`` list contains tuples of the form
    ``("heading", level, text)`` or ``("paragraph", None, text)``.
    """

    def __init__(self) -> None:
        super().__init__(convert_charrefs=True)
        self.blocks: List[_Block] = []
        self._current_kind: str | None = None
        self._current_level: int | None = None
        self._buffer: List[str] = []

    # Internal helpers -------------------------------------------------

    def _flush(self) -> None:
        if not self._current_kind:
            return
        text = "".join(self._buffer).strip()
        self._buffer.clear()
        if not text:
            self._current_kind = None
            self._current_level = None
            return
        if self._current_kind == "heading":
            self.blocks.append(("heading", self._current_level, text))
        else:
            self.blocks.append(("paragraph", None, text))
        self._current_kind = None
        self._current_level = None

    # HTMLParser overrides ---------------------------------------------

    def handle_starttag(self, tag: str, attrs):  # type: ignore[override]
        tag = tag.lower()
        if tag == "p" or tag in {"h1", "h2", "h3", "h4", "h5", "h6"}:
            self._flush()
            if tag == "p":
                self._current_kind = "paragraph"
                self._current_level = None
            else:
                self._current_kind = "heading"
                self._current_level = int(tag[1])
            self._buffer = []

    def handle_endtag(self, tag: str) -> None:  # type: ignore[override]
        tag = tag.lower()
        if tag == "p" or tag in {"h1", "h2", "h3", "h4", "h5", "h6"}:
            self._flush()

    def handle_data(self, data: str) -> None:  # type: ignore[override]
        if self._current_kind:
            self._buffer.append(data)

    def close(self) -> None:  # type: ignore[override]
        self._flush()
        super().close()


class HtmlParser(BaseParser):
    """Parser for basic HTML documents from web ingestion.

    We primarily target content ingested via :class:`WebIngestor` where
    ``raw.source_type`` is :enum:`SourceType.WEB`.
    """

    def supports(self, raw: RawDocument) -> bool:  # pragma: no cover - tiny helper
        if raw.source_type is SourceType.WEB:
            return True
        text = raw.content.lower()
        return "<html" in text and "</html" in text

    def parse(self, raw: RawDocument) -> DocumentTree:
        extractor = _HTMLBlockExtractor()
        extractor.feed(raw.content)
        extractor.close()

        root = DocumentNode(
            id=f"{raw.id}#root",
            node_type=NodeType.DOCUMENT,
            text=None,
            metadata={},
        )

        current_section = root
        counter = 0

        for kind, level, text in extractor.blocks:
            if kind == "heading":
                counter += 1
                section = DocumentNode(
                    id=f"{raw.id}#s{counter}",
                    node_type=NodeType.SECTION,
                    text=text,
                    metadata={"level": level or 1},
                )
                root.children.append(section)
                current_section = section
            else:  # paragraph
                counter += 1
                parent = current_section or root
                para = DocumentNode(
                    id=f"{raw.id}#p{counter}",
                    node_type=NodeType.PARAGRAPH,
                    text=text,
                    metadata={},
                )
                parent.children.append(para)

        return DocumentTree(root=root, metadata={"source_id": raw.id, "parser": "html"})

