"""
Markdown parser — uses mistletoe AST for full GFM support.

Walks the AST and maps nodes to RawElement records:
- Heading -> structural:section_header or structural:title
- Paragraph -> text:narrative
- Table -> table:simple (dual-format)
- CodeFence / CodeBlock -> code:block
- List / ListItem -> list:item / list:item_ordered
- BlockQuote -> text:pull_quote or text:admonition (GFM alerts)
- Image -> media:figure
"""

from __future__ import annotations

from collections.abc import Iterator
from typing import Any

from .base import BaseParser, FileRecord, RawElement

# GFM alert kinds: > [!NOTE], > [!WARNING], etc.
_GFM_ALERT_KINDS = {"note", "warning", "tip", "caution", "important", "danger"}


def _node_text(node: Any) -> str:
    """Recursively extract plain text from a mistletoe AST node."""
    node_type = type(node).__name__
    # LineBreak nodes emit a newline so GFM alert detection works correctly
    if node_type == "LineBreak":
        return "\n"
    # Always prefer children over content — mistletoe 1.x Heading.content is unreliable
    if hasattr(node, "children") and node.children:
        return "".join(_node_text(c) for c in node.children)
    if hasattr(node, "content") and isinstance(node.content, str):
        return node.content
    if hasattr(node, "target"):  # links
        return getattr(node, "content", "") or ""
    return ""


def _table_from_node(node: Any) -> RawElement:
    """Convert a mistletoe Table AST node to dual-format RawElement."""
    headers: list[str] = []
    rows: list[list[str]] = []

    if hasattr(node, "header") and node.header:
        headers = [_node_text(cell).strip() for cell in node.header.children]

    if hasattr(node, "children"):
        for row_node in node.children:
            if hasattr(row_node, "children"):
                row = [_node_text(cell).strip() for cell in row_node.children]
                rows.append(row)

    # Build GFM markdown
    lines: list[str] = []
    if headers:
        lines.append("| " + " | ".join(headers) + " |")
        lines.append("|" + "|".join(" --- " for _ in headers) + "|")
    for row in rows:
        padded = list(row) + [""] * max(0, len(headers) - len(row))
        lines.append("| " + " | ".join(padded) + " |")
    markdown = "\n".join(lines)

    return RawElement(
        element_type="table:simple",
        text=markdown,
        table_data={
            "markdown": markdown,
            "structured": {"headers": headers, "rows": rows},
            "has_header_row": bool(headers),
            "row_count": len(rows),
            "header_row_count": 1 if headers else 0,
            "col_count": len(headers) if headers else (len(rows[0]) if rows else 0),
        },
        extra={"extraction_method": "mistletoe:table", "confidence": 0.97},
    )


def _walk_ast(node: Any, list_depth: int = 0, list_first_seq: int = 0) -> Iterator[RawElement]:
    """Recursively walk mistletoe AST and yield RawElement records."""
    node_type = type(node).__name__

    if node_type == "Heading":
        level = getattr(node, "level", 1)
        text = _node_text(node).strip()
        if text:
            yield RawElement(
                element_type="structural:section_header" if level > 1 else "structural:title",
                text=text,
                heading_level=min(level, 6),
                detection_tier=1,
                extra={"extraction_method": "mistletoe:heading", "confidence": 0.99},
            )
        return  # don't recurse into heading children

    elif node_type == "Paragraph":
        text = _node_text(node).strip()
        if not text:
            return

        # Check for display math: paragraph that is entirely $$...$$
        stripped = text.strip()
        if stripped.startswith("$$") and stripped.endswith("$$") and len(stripped) > 4:
            latex = stripped[2:-2].strip()
            if latex:
                yield RawElement(
                    element_type="scientific:equation_display",
                    text=latex,
                    equation_data={"latex": latex, "label": None},
                    extra={"extraction_method": "mistletoe:display-math", "confidence": 0.95},
                )
                return

        # Check for GFM alert pattern: [!NOTE], [!WARNING] etc.
        # These appear as the first line of a blockquote paragraph
        first_line = text.split("\n")[0].strip()
        if first_line.startswith("[!") and first_line.endswith("]"):
            kind_candidate = first_line[2:-1].lower()
            if kind_candidate in _GFM_ALERT_KINDS:
                body = text[len(first_line) :].strip()
                yield RawElement(
                    element_type="text:admonition",
                    text=body or text,
                    admonition_data={"kind": kind_candidate, "title": kind_candidate.title()},
                    extra={"extraction_method": "mistletoe:gfm-alert", "confidence": 0.98},
                )
                return

        yield RawElement(
            element_type="text:narrative",
            text=text,
            extra={"extraction_method": "mistletoe:paragraph", "confidence": 0.95},
        )
        return

    elif node_type == "Table":
        yield _table_from_node(node)
        return

    elif node_type in ("CodeFence", "BlockCode"):
        code_text = ""
        lang = None
        if hasattr(node, "children") and node.children:
            code_text = _node_text(node.children[0]).strip() if node.children else ""
        if hasattr(node, "language"):
            lang = node.language or None
        if hasattr(node, "info_string"):
            lang = node.info_string or lang
        # For BlockCode, content is in .content
        if hasattr(node, "content") and isinstance(node.content, str):
            code_text = node.content.strip()
        if code_text:
            yield RawElement(
                element_type="code:block",
                text=code_text,
                extra={
                    "extraction_method": "mistletoe:code-fence",
                    "confidence": 0.99,
                    "language": lang,
                },
            )
        return

    elif node_type in ("BlockQuote", "Quote"):
        # mistletoe 1.x uses 'Quote', older versions use 'BlockQuote'
        # Recurse into children — GFM alerts are detected in Paragraph handler
        if hasattr(node, "children"):
            for child in node.children:
                yield from _walk_ast(child, list_depth, list_first_seq)
        return

    elif node_type in ("List", "ListItem"):
        if node_type == "List":
            is_ordered = getattr(node, "start", None) is not None
            if hasattr(node, "children"):
                for idx, item in enumerate(node.children):
                    item_text = _node_text(item).strip()
                    if item_text:
                        yield RawElement(
                            element_type="list:item_ordered" if is_ordered else "list:item",
                            text=item_text,
                            list_data={
                                "list_index": idx,
                                "list_depth": list_depth,
                                "first_item_sequence_index": list_first_seq,
                            },
                            extra={"extraction_method": "mistletoe:list", "confidence": 0.95},
                        )
                    # Recurse for nested lists
                    if hasattr(item, "children"):
                        for child in item.children:
                            if type(child).__name__ == "List":
                                yield from _walk_ast(child, list_depth + 1, list_first_seq)
        return

    elif node_type == "Image":
        alt = _node_text(node).strip()
        src = getattr(node, "src", None) or getattr(node, "dest", None) or ""
        yield RawElement(
            element_type="media:figure",
            text=alt or src,
            figure_data={
                "caption": alt,
                "image_ref": src,
                "image_sha256": None,
                "ocr_text": None,
            },
            extra={"extraction_method": "mistletoe:image", "confidence": 0.90},
        )
        return

    elif node_type == "ThematicBreak":
        yield RawElement(
            element_type="structural:divider",
            text="---",
            extra={"extraction_method": "mistletoe:thematic-break", "confidence": 1.0},
        )
        return

    # Recurse into all other nodes
    if hasattr(node, "children") and node.children:
        for child in node.children:
            yield from _walk_ast(child, list_depth, list_first_seq)


class MarkdownParser(BaseParser):
    handles = [
        ("text/markdown", None),
        ("text/x-markdown", None),
    ]

    def parse(self, file_record: FileRecord) -> Iterator[RawElement]:
        try:
            from mistletoe import Document as MistletoeDocument
        except ImportError as e:
            raise ImportError("mistletoe required: pip install mistletoe") from e

        try:
            from charset_normalizer import from_path

            result = from_path(str(file_record.path)).best()
            text = (
                str(result)
                if result
                else file_record.path.read_text(encoding="utf-8", errors="replace")
            )
        except Exception:
            text = file_record.path.read_text(encoding="utf-8", errors="replace")

        doc = MistletoeDocument(text)

        if hasattr(doc, "children") and doc.children:
            for node in doc.children:
                yield from _walk_ast(node)
