"""解析 .docx → Document 对象。

文档顺序遍历 body：顶级段落 + 表格内段落都纳入 paragraphs，
后续 role tagging 与规则评估都能扫到表格内的内容。
"""

from __future__ import annotations
from pathlib import Path
from docx import Document as DocxDocument
from docx.oxml.ns import qn
from docx.text.paragraph import Paragraph as DocxParagraph
from lxml import etree

from fastapi_modules.fastapi_leaudit.govdoc_engine.models import Document, Paragraph, ParagraphStyle, Run, Table
from fastapi_modules.fastapi_leaudit.govdoc_engine.parser.style_resolver import StyleResolver


_ALIGN_MAP = {0: "left", 1: "center", 2: "right", 3: "justify"}


def _read_run_style(run, p_elem, resolver: StyleResolver) -> ParagraphStyle:
    rs = resolver.resolve_run(p_elem, run._element)
    return ParagraphStyle(
        font_eastasia=rs.font_eastasia,
        font_ascii=rs.font_ascii,
        font_size_pt=rs.size_pt,
        bold=bool(rs.bold) if rs.bold is not None else False,
        italic=bool(rs.italic) if rs.italic is not None else False,
    )


def _read_paragraph_style(p, resolver: StyleResolver) -> ParagraphStyle:
    pf = p.paragraph_format
    alignment = (
        _ALIGN_MAP.get(pf.alignment, "left") if pf.alignment is not None else "left"
    )
    spacing_pt = float(pf.line_spacing) if pf.line_spacing is not None else None
    indent = pf.first_line_indent
    indent_pt = float(indent.pt) if indent is not None else 0.0

    if p.runs:
        base = _read_run_style(p.runs[0], p._element, resolver)
    else:
        rs = resolver.resolve_paragraph(p._element)
        base = ParagraphStyle(
            font_eastasia=rs.font_eastasia,
            font_ascii=rs.font_ascii,
            font_size_pt=rs.size_pt,
            bold=bool(rs.bold) if rs.bold is not None else False,
            italic=bool(rs.italic) if rs.italic is not None else False,
        )
    base.alignment = alignment
    base.line_spacing = spacing_pt
    base.first_line_indent_pt = indent_pt
    return base


def _is_in_table(p_elem) -> bool:
    parent = p_elem.getparent()
    while parent is not None:
        if etree.QName(parent).localname == "tbl":
            return True
        parent = parent.getparent()
    return False


def _iter_body_paragraphs(docx):
    """文档顺序遍历 body 下所有 w:p（含表格内）。"""
    for p_elem in docx.element.body.iter(qn("w:p")):
        yield p_elem


def _iter_header_footer_paragraphs(docx):
    """yield (DocxParagraph, p_elem, in_header, in_footer)，跨 section 去重。"""
    seen: set[int] = set()
    for section in docx.sections:
        targets = [
            ("header", section.header),
            ("first_header", section.first_page_header),
            ("even_header", section.even_page_header),
            ("footer", section.footer),
            ("first_footer", section.first_page_footer),
            ("even_footer", section.even_page_footer),
        ]
        for kind, hf in targets:
            if hf is None:
                continue
            try:
                if hf.is_linked_to_previous:
                    continue
            except Exception:
                pass
            in_header = "header" in kind
            for p in hf.paragraphs:
                pid = id(p._element)
                if pid in seen:
                    continue
                seen.add(pid)
                yield p, p._element, in_header, not in_header


def parse_docx(path: str | Path) -> Document:
    path = Path(path)
    docx = DocxDocument(path)
    resolver = StyleResolver(docx)

    paragraphs: list[Paragraph] = []
    idx = 0
    # 1) body：含表格内段落
    for p_elem in _iter_body_paragraphs(docx):
        p = DocxParagraph(p_elem, docx.part)
        runs = [
            Run(text=r.text, style=_read_run_style(r, p_elem, resolver))
            for r in p.runs
        ]
        style = _read_paragraph_style(p, resolver)
        paragraphs.append(Paragraph(
            index=idx,
            text=p.text,
            runs=runs,
            style=style,
            in_table=_is_in_table(p_elem),
        ))
        idx += 1
    # 2) headers / footers：附在末尾，role tagger 也能扫到
    for p, p_elem, in_header, in_footer in _iter_header_footer_paragraphs(docx):
        runs = [
            Run(text=r.text, style=_read_run_style(r, p_elem, resolver))
            for r in p.runs
        ]
        style = _read_paragraph_style(p, resolver)
        paragraphs.append(Paragraph(
            index=idx,
            text=p.text,
            runs=runs,
            style=style,
            in_table=_is_in_table(p_elem),
            in_header=in_header,
            in_footer=in_footer,
        ))
        idx += 1

    tables = []
    for tidx, t in enumerate(docx.tables):
        rows = [[cell.text for cell in row.cells] for row in t.rows]
        tables.append(Table(index=tidx, rows=rows))

    return Document(
        meta={"path": str(path), "page_count": len(docx.sections)},
        paragraphs=paragraphs,
        tables=tables,
    )
