"""Convert a Word .docx document to Markdown, preserving structure, inline
formatting and tables in their original document order."""

import os
from lxml import etree

from format_table_to_markdown import format_table_to_markdown


# XML namespace used by python-docx element tags
_W = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
_TAG_P = f"{{{_W}}}p"
_TAG_TBL = f"{{{_W}}}tbl"
_TAG_TR = f"{{{_W}}}tr"
_TAG_TC = f"{{{_W}}}tc"
_TAG_R = f"{{{_W}}}r"
_TAG_T = f"{{{_W}}}t"
_TAG_RPR = f"{{{_W}}}rPr"
_TAG_B = f"{{{_W}}}b"
_TAG_I = f"{{{_W}}}i"
_TAG_U = f"{{{_W}}}u"
_TAG_PSTYLE = f"{{{_W}}}pStyle"
_TAG_PPR = f"{{{_W}}}pPr"


def _heading_level(paragraph) -> int:
    """Return heading level (1-6) if the paragraph is a heading, else 0."""
    pPr = paragraph._p.find(_TAG_PPR)
    if pPr is None:
        return 0
    pStyle = pPr.find(_TAG_PSTYLE)
    if pStyle is None:
        return 0
    val = pStyle.get(f"{{{_W}}}val", "")
    if val.lower().startswith("heading"):
        parts = val.split()
        if len(parts) == 2:
            try:
                return int(parts[1])
            except ValueError:
                pass
        # Some locales use "Heading1" (no space)
        suffix = val[len("heading"):]
        if suffix.isdigit():
            return int(suffix)
    return 0


def _run_to_md(run_elem) -> str:
    """Convert a single <w:r> element to a markdown-formatted string."""
    # Collect text
    text_parts = []
    for t in run_elem.findall(_TAG_T):
        text_parts.append(t.text or "")
    text = "".join(text_parts)
    if not text:
        return ""

    # Read formatting from <w:rPr>
    rPr = run_elem.find(_TAG_RPR)
    bold = False
    italic = False
    underline = False
    if rPr is not None:
        bold = rPr.find(_TAG_B) is not None
        italic = rPr.find(_TAG_I) is not None
        u_elem = rPr.find(_TAG_U)
        if u_elem is not None:
            u_val = u_elem.get(f"{{{_W}}}val", "")
            underline = u_val not in ("none", "")

    # Apply markdown formatting (innermost first: underline → italic → bold)
    if underline:
        text = f"<ins>{text}</ins>"
    if italic:
        text = f"*{text}*"
    if bold:
        text = f"**{text}**"
    return text


def _paragraph_to_md(paragraph) -> str:
    """Convert a python-docx Paragraph to a markdown string."""
    level = _heading_level(paragraph)
    runs_md = "".join(
        _run_to_md(elem)
        for elem in paragraph._p
        if elem.tag == _TAG_R
    )
    if level:
        return f"{'#' * level} {runs_md}"
    return runs_md


def _table_to_md(table) -> str:
    """Convert a python-docx Table to a markdown table string."""
    rows: list[list[str]] = []
    for row in table.rows:
        cells = []
        for cell in row.cells:
            # Join all paragraphs in the cell with a space
            cell_text = " ".join(p.text for p in cell.paragraphs).strip()
            cells.append(cell_text)
        rows.append(cells)
    return format_table_to_markdown(rows, has_header=True)


def docx_to_markdown(docx_path: str) -> str:
    """Convert a Word .docx document to Markdown.

    Preserves document structure (headings), inline formatting (bold, italic,
    underline) and tables in their original position.

    Args:
        docx_path: Absolute or relative path to the .docx file.

    Returns:
        Markdown string representing the document.

    Raises:
        FileNotFoundError: If the file does not exist.
        Exception: If the file cannot be parsed as a .docx document.
    """
    import docx  # deferred so the module is importable without python-docx installed

    if not os.path.exists(docx_path):
        raise FileNotFoundError(f"File not found: {docx_path}")

    doc = docx.Document(docx_path)

    # Build a mapping from the XML element id to the Table object for O(1) lookup
    table_map: dict[int, object] = {
        id(table._tbl): table for table in doc.tables
    }

    parts: list[str] = []

    for child in doc.element.body:
        if child.tag == _TAG_P:
            # Wrap in a temporary paragraph object to reuse _paragraph_to_md
            from docx.text.paragraph import Paragraph
            para = Paragraph(child, doc)
            md = _paragraph_to_md(para)
            if md.strip():
                parts.append(md)
        elif child.tag == _TAG_TBL:
            table = table_map.get(id(child))
            if table is not None:
                md = _table_to_md(table)
                if md:
                    parts.append(md)

    return "\n\n".join(parts)
