"""Markdown to OOXML converter for TokenMoulds MCP.

Converts markdown content to structured content blocks suitable for
Word document generation, preserving inline formatting (bold, italic, etc.)
and supporting tables, lists, blockquotes, code blocks, and more.

Uses mistune for parsing with full GFM (GitHub Flavored Markdown) support.
"""

from __future__ import annotations

from typing import Any, Dict, List

import mistune
from mistune.plugins.table import table
from mistune.plugins.formatting import strikethrough


def _create_parser() -> mistune.Markdown:
    """Create mistune parser with plugins."""
    md = mistune.create_markdown(renderer=None)
    table(md)
    strikethrough(md)
    return md


def _extract_inline_runs(children: List[Dict]) -> List[Dict[str, Any]]:
    """Convert inline AST nodes to text runs with formatting.

    Returns list of runs: [{"text": "...", "bold": True, "italic": False, ...}]
    """
    runs = []

    for child in children:
        node_type = child.get("type")

        if node_type == "text":
            runs.append({"text": child.get("raw", "")})

        elif node_type == "strong":
            # Bold text
            for sub in _extract_inline_runs(child.get("children", [])):
                sub["bold"] = True
                runs.append(sub)

        elif node_type == "emphasis":
            # Italic text
            for sub in _extract_inline_runs(child.get("children", [])):
                sub["italic"] = True
                runs.append(sub)

        elif node_type == "strikethrough":
            # Strikethrough text
            for sub in _extract_inline_runs(child.get("children", [])):
                sub["strikethrough"] = True
                runs.append(sub)

        elif node_type == "codespan":
            # Inline code
            runs.append(
                {
                    "text": child.get("raw", ""),
                    "code": True,
                }
            )

        elif node_type == "link":
            # Hyperlink
            url = child.get("attrs", {}).get("url", "")
            link_text = ""
            for sub in child.get("children", []):
                if sub.get("type") == "text":
                    link_text += sub.get("raw", "")
            runs.append(
                {
                    "text": link_text or url,
                    "link": url,
                }
            )

        elif node_type == "softbreak":
            # Soft line break - just a space
            runs.append({"text": " "})

        elif node_type == "linebreak":
            # Hard line break
            runs.append({"text": "\n", "linebreak": True})

        elif node_type == "image":
            # Image with URL and alt text
            # Alt text is in children[0].raw (mistune puts it in children, not attrs)
            attrs = child.get("attrs", {})
            url = attrs.get("url", "")
            alt = ""
            for c in child.get("children", []):
                if c.get("type") == "text":
                    alt += c.get("raw", "")
            runs.append(
                {
                    "image": True,
                    "url": url,
                    "alt": alt,
                    "text": f"[Image: {alt}]" if alt else "[Image]",  # Fallback text
                }
            )

    return runs


def _process_list(node: Dict, ordered: bool) -> List[Dict[str, Any]]:
    """Process list node into content blocks."""
    blocks = []

    for item in node.get("children", []):
        if item.get("type") == "list_item":
            # Get item content
            for content in item.get("children", []):
                if content.get("type") in ("paragraph", "block_text"):
                    blocks.append(
                        {
                            "type": "numbered_list" if ordered else "bullet_list",
                            "runs": _extract_inline_runs(content.get("children", [])),
                        }
                    )
                elif content.get("type") == "list":
                    # Nested list
                    nested_ordered = content.get("attrs", {}).get("ordered", False)
                    nested_blocks = _process_list(content, nested_ordered)
                    for nb in nested_blocks:
                        nb["indent"] = nb.get("indent", 0) + 1
                    blocks.extend(nested_blocks)

    return blocks


def _process_table(node: Dict) -> Dict[str, Any]:
    """Process table node into content block."""
    rows = []

    for child in node.get("children", []):
        if child.get("type") == "table_head":
            # Header row
            header_cells = []
            for cell in child.get("children", []):
                if cell.get("type") == "table_cell":
                    header_cells.append(
                        {
                            "runs": _extract_inline_runs(cell.get("children", [])),
                            "header": True,
                        }
                    )
            if header_cells:
                rows.append(header_cells)

        elif child.get("type") == "table_body":
            # Body rows
            for row in child.get("children", []):
                if row.get("type") == "table_row":
                    row_cells = []
                    for cell in row.get("children", []):
                        if cell.get("type") == "table_cell":
                            row_cells.append(
                                {
                                    "runs": _extract_inline_runs(
                                        cell.get("children", [])
                                    ),
                                    "header": False,
                                }
                            )
                    if row_cells:
                        rows.append(row_cells)

    return {
        "type": "table",
        "rows": rows,
    }


def markdown_to_blocks(markdown: str) -> List[Dict[str, Any]]:
    """Convert markdown to content blocks.

    Supports:
    - Headings (# ## ### ####)
    - Paragraphs with inline formatting (bold, italic, strikethrough)
    - Bullet lists (- *)
    - Numbered lists (1. 2.)
    - Tables (GFM)
    - Code blocks (```)
    - Inline code (`code`)
    - Block quotes (>)
    - Horizontal rules (---)
    - Links [text](url)

    Args:
        markdown: Markdown content string

    Returns:
        List of content blocks with structure like:
        [
            {"type": "heading1", "runs": [{"text": "Title"}]},
            {"type": "paragraph", "runs": [{"text": "Normal "}, {"text": "bold", "bold": True}]},
            {"type": "bullet_list", "runs": [{"text": "Item"}]},
            {"type": "table", "rows": [[{"runs": [...], "header": True}]]},
        ]
    """
    parser = _create_parser()
    tokens = parser(markdown)

    blocks: List[Dict[str, Any]] = []

    for token in tokens:
        if not isinstance(token, dict):
            continue
        token_type = token.get("type")

        if token_type == "heading":
            level = token.get("attrs", {}).get("level", 1)
            level = min(level, 4)  # Cap at heading4
            blocks.append(
                {
                    "type": f"heading{level}",
                    "runs": _extract_inline_runs(token.get("children", [])),
                }
            )

        elif token_type == "paragraph":
            blocks.append(
                {
                    "type": "paragraph",
                    "runs": _extract_inline_runs(token.get("children", [])),
                }
            )

        elif token_type == "list":
            ordered = token.get("attrs", {}).get("ordered", False)
            blocks.extend(_process_list(token, ordered))

        elif token_type == "table":
            blocks.append(_process_table(token))

        elif token_type == "block_code":
            language = token.get("attrs", {}).get("info", "")
            blocks.append(
                {
                    "type": "code_block",
                    "text": token.get("raw", ""),
                    "language": language,
                }
            )

        elif token_type == "block_quote":
            # Process blockquote children
            for child in token.get("children", []):
                if child.get("type") == "paragraph":
                    blocks.append(
                        {
                            "type": "block_quote",
                            "runs": _extract_inline_runs(child.get("children", [])),
                        }
                    )

        elif token_type == "thematic_break":
            blocks.append({"type": "horizontal_rule"})

        elif token_type == "blank_line":
            # Skip blank lines
            pass

    return blocks
