"""
Word document exporter for g-gremlin.

Converts markdown content to .docx format using python-docx.
Designed for Microsoft 365 shops that prefer Word over Google Docs/Notion.

v0 scope:
- Headings (# through ######)
- Paragraphs
- Bullet lists (-, *, +)
- Numbered lists (1., 2., etc.)
- Code blocks (``` fenced) with monospace font
- Basic pipe tables (| col | col |)
- Strip inline markdown (**bold**, *italic*, `code`)

Requires: pip install python-docx (or: pip install g-gremlin[docs-word])
"""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import List, Optional, TYPE_CHECKING

# Lazy import to avoid hard dependency
_docx_available: Optional[bool] = None

if TYPE_CHECKING:
    from docx import Document


def _check_docx_available() -> None:
    """Check if python-docx is installed, raise helpful error if not."""
    global _docx_available
    if _docx_available is None:
        try:
            import docx  # noqa: F401

            _docx_available = True
        except ImportError:
            _docx_available = False

    if not _docx_available:
        raise ImportError(
            "python-docx is required for Word export. "
            "Install with: pip install python-docx "
            "or: pip install g-gremlin[docs-word]"
        )


# Regex patterns for markdown parsing
_HEADING_RE = re.compile(r"^(#{1,6})\s+(.*)$")
_BULLET_RE = re.compile(r"^\s*[-*+]\s+(.*)$")
_NUMBERED_RE = re.compile(r"^\s*\d+\.\s+(.*)$")
_CODE_FENCE_RE = re.compile(r"^\s*```(\w*)$")
_CODE_FENCE_END_RE = re.compile(r"^\s*```\s*$")
_TABLE_ROW_RE = re.compile(r"^\s*\|.*\|\s*$")
_TABLE_SEPARATOR_RE = re.compile(r"^\s*\|[\s\-:|]+\|\s*$")

# Inline markdown patterns
_BOLD_RE = re.compile(r"\*\*(.+?)\*\*|__(.+?)__")
_ITALIC_RE = re.compile(r"(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)|(?<!_)_(?!_)(.+?)(?<!_)_(?!_)")
_CODE_RE = re.compile(r"`([^`]+)`")


@dataclass
class WordExportOptions:
    """Configuration options for Word export."""

    title_style: str = "Title"
    code_font: str = "Consolas"
    code_size_pt: int = 9
    body_size_pt: int = 11
    preserve_blank_lines: bool = True
    # Table styling
    table_style: str = "Table Grid"


@dataclass
class WordExportResult:
    """Result of a Word export operation."""

    success: bool
    output_path: Optional[Path] = None
    error: Optional[str] = None
    pages_exported: int = 0
    warnings: List[str] = field(default_factory=list)


class WordExporter:
    """
    Markdown to Word document exporter.

    Usage:
        exporter = WordExporter()
        result = exporter.export(
            title="My Document",
            markdown="# Heading\\n\\nSome content...",
            out_path=Path("output.docx")
        )
    """

    def __init__(self, options: Optional[WordExportOptions] = None) -> None:
        self.options = options or WordExportOptions()

    def export(
        self,
        *,
        title: str,
        markdown: str,
        out_path: Path,
    ) -> WordExportResult:
        """
        Export markdown content to a Word document.

        Args:
            title: Document title (added as Title style paragraph)
            markdown: Markdown content to convert
            out_path: Output file path (.docx)

        Returns:
            WordExportResult with success status and output path
        """
        _check_docx_available()

        from docx import Document

        try:
            # Ensure output directory exists
            out_path.parent.mkdir(parents=True, exist_ok=True)

            doc = Document()

            # Add title
            if title:
                doc.add_paragraph(title, style=self.options.title_style)

            # Parse and render markdown
            lines = markdown.splitlines()
            i = 0
            in_code = False
            code_lines: List[str] = []
            code_language: Optional[str] = None

            while i < len(lines):
                line = lines[i]

                # Code fence start
                fence_match = _CODE_FENCE_RE.match(line)
                if fence_match and not in_code:
                    in_code = True
                    code_lines = []
                    code_language = fence_match.group(1) or None
                    i += 1
                    continue

                # Code fence end
                if in_code and _CODE_FENCE_END_RE.match(line):
                    self._add_code_block(doc, "\n".join(code_lines), code_language)
                    in_code = False
                    code_lines = []
                    code_language = None
                    i += 1
                    continue

                # Inside code block
                if in_code:
                    code_lines.append(line)
                    i += 1
                    continue

                # Table detection
                if _TABLE_ROW_RE.match(line) and self._looks_like_table(lines, i):
                    i = self._add_table(doc, lines, i)
                    continue

                # Heading
                heading_match = _HEADING_RE.match(line)
                if heading_match:
                    level = len(heading_match.group(1))
                    text = heading_match.group(2).strip()
                    doc.add_heading(self._strip_inline_md(text), level=level)
                    i += 1
                    continue

                # Bullet list
                bullet_match = _BULLET_RE.match(line)
                if bullet_match:
                    text = bullet_match.group(1).strip()
                    doc.add_paragraph(self._strip_inline_md(text), style="List Bullet")
                    i += 1
                    continue

                # Numbered list
                numbered_match = _NUMBERED_RE.match(line)
                if numbered_match:
                    text = numbered_match.group(1).strip()
                    doc.add_paragraph(self._strip_inline_md(text), style="List Number")
                    i += 1
                    continue

                # Blank line
                if line.strip() == "":
                    if self.options.preserve_blank_lines:
                        doc.add_paragraph("")
                    i += 1
                    continue

                # Normal paragraph
                doc.add_paragraph(self._strip_inline_md(line))
                i += 1

            # Handle unclosed code block (graceful degradation)
            if in_code and code_lines:
                self._add_code_block(doc, "\n".join(code_lines), code_language)

            # Save document
            doc.save(str(out_path))

            return WordExportResult(
                success=True,
                output_path=out_path,
                pages_exported=1,
            )

        except Exception as exc:
            return WordExportResult(
                success=False,
                error=str(exc),
            )

    def _add_code_block(
        self,
        doc: "Document",
        code: str,
        language: Optional[str] = None,
    ) -> None:
        """Add a code block with monospace formatting."""
        from docx.shared import Pt
        from docx.oxml.ns import qn

        p = doc.add_paragraph()
        run = p.add_run(code)
        run.font.name = self.options.code_font
        # Set font for all character sets (required for some Word versions)
        run._element.rPr.rFonts.set(qn("w:eastAsia"), self.options.code_font)
        run.font.size = Pt(self.options.code_size_pt)

    def _strip_inline_md(self, text: str) -> str:
        """
        Strip inline markdown formatting, keeping the text content.

        v0: Simple removal of markers without preserving formatting in Word.
        """
        # Remove bold markers
        text = _BOLD_RE.sub(r"\1\2", text)
        # Remove italic markers
        text = _ITALIC_RE.sub(r"\1\2", text)
        # Remove inline code markers
        text = _CODE_RE.sub(r"\1", text)
        return text

    def _looks_like_table(self, lines: List[str], start: int) -> bool:
        """
        Check if lines starting at start form a valid markdown table.

        Requires at least header row + separator row.
        """
        if start + 1 >= len(lines):
            return False
        sep = lines[start + 1].strip()
        return sep.startswith("|") and ("---" in sep or ":--" in sep or "--:" in sep)

    def _add_table(self, doc: "Document", lines: List[str], start: int) -> int:
        """
        Parse and add a markdown table to the document.

        Returns the index of the first line after the table.
        """
        # Collect contiguous table rows
        rows_raw: List[str] = []
        i = start
        while i < len(lines) and _TABLE_ROW_RE.match(lines[i]):
            rows_raw.append(lines[i].strip())
            i += 1

        # Parse rows (skip separator row)
        parsed_rows: List[List[str]] = []
        for idx, row in enumerate(rows_raw):
            # Skip separator row (usually row index 1)
            if _TABLE_SEPARATOR_RE.match(row):
                continue
            cells = self._parse_table_row(row)
            parsed_rows.append(cells)

        if len(parsed_rows) < 1:
            return i

        # Determine column count
        col_count = max(len(row) for row in parsed_rows)

        # Create table
        table = doc.add_table(rows=len(parsed_rows), cols=col_count)
        table.style = self.options.table_style

        # Populate cells
        for row_idx, row_cells in enumerate(parsed_rows):
            for col_idx in range(col_count):
                cell_text = row_cells[col_idx] if col_idx < len(row_cells) else ""
                table.cell(row_idx, col_idx).text = self._strip_inline_md(cell_text)

        return i

    def _parse_table_row(self, row: str) -> List[str]:
        """Parse a markdown table row into cells."""
        # "| a | b |" -> ["a", "b"]
        inner = row.strip().strip("|")
        parts = [p.strip() for p in inner.split("|")]
        return parts


__all__ = [
    "WordExporter",
    "WordExportOptions",
    "WordExportResult",
]
