from __future__ import annotations

import re
from pathlib import Path
from typing import List, Optional
from docx import Document
from docx.oxml.text.paragraph import CT_P
from docx.oxml.table import CT_Tbl
from docx.text.paragraph import Paragraph
from docx.table import Table

from ..models import TableItem
from ..utils.title_table_footnote_patterns_loader import (
    load_title_patterns, load_label_patterns, load_footnote_patterns
)

TITLE_PATTERNS: List[re.Pattern] = load_title_patterns()
LABEL_PATTERNS: List[re.Pattern] = load_label_patterns()
FOOTNOTE_PATTERNS: List[re.Pattern] = load_footnote_patterns()


def extract_title_label(text: str) -> Optional[str]:
    """
    从标题文本中提取简洁标签（例如："Table 2.3" 或 "Listing 1"）。
    仅返回前缀 + 编号，不包含后续描述。
    支持从配置的多个正则中按序匹配，取首个命中。
    """
    text = (text or "").strip()
    for pat in LABEL_PATTERNS:
        m = pat.match(text)
        if m:
            label = m.group(0).strip()
            label = re.sub(r'\s+', ' ', label)
            # 统一大小写（前缀首字母大写）
            label = re.sub(r'^(table|listing)', lambda m: m.group(1).capitalize(), label)
            return label
    return None


def _iter_block_items(doc: Document):
    """
    迭代文档正文的块级元素（段落与表格），保持与文档中出现的真实顺序。

    返回值:
    - 当遇到段落时，产出 ('paragraph', Paragraph)
    - 当遇到表格时，产出 ('table', Table)

    说明:
    - 直接遍历底层 body.iterchildren()，避免仅用 doc.paragraphs 或 doc.tables 导致顺序错乱。
    - 该生成器用于在标题、表格、脚注之间建立基于相邻关系的解析。
    """
    body = doc._element.body
    for child in body.iterchildren():
        if isinstance(child, CT_P):
            yield 'paragraph', Paragraph(child, doc)
        elif isinstance(child, CT_Tbl):
            yield 'table', Table(child, doc)


def _is_probable_footnote(text: str, style_name: str) -> bool:
    """判断段落是否很可能是脚注文本。
    规则：匹配 FOOTNOTE_PATTERNS；或以项目符号/破折号开头；排除 Heading。
    """
    if not text:
        return False
    if style_name and style_name.startswith('Heading'):
        return False
    if any(p.search(text) for p in FOOTNOTE_PATTERNS):
        return True
    if re.match(r"^[\-•·–]\s+", text):
        return True
    return False


def extract_docx_content_with_metadata(docx_path: str) -> List[TableItem]:
    """
    解析文档，记录每个 section 的元数据（段落索引、表格索引）。
    遍历文档块（段落/表格）以保证表格关联到最近的标题。
    同时为每个表格单独采集其后的脚注段落索引，避免整段脚注复制到多个表格。
    标题匹配规则来自 YAML 配置（pipelines/title_table_footnote_patterns.yaml），若缺失则回退默认。
    另外：在解析阶段将脚注段落文本累积到 TableItem.footnote（以换行分隔）。
    对于无表格的标题段落（如 "Same as Table X"），也收集紧随其后的脚注文本。
    """
    doc = Document(docx_path)
    content: List[TableItem] = []

    # 建立索引映射：paragraph 和 table 的底层元素到数组索引
    para_index_map = {para._p: idx for idx, para in enumerate(doc.paragraphs)}
    tbl_index_map = {tbl._tbl: idx for idx, tbl in enumerate(doc.tables)}

    current_section: Optional[TableItem] = None
    collecting_title = False  # 是否在收集多行标题
    has_active_table: bool = False  # 标记当前 section 是否已出现表格，用于收集脚注

    def compute_title_level(label: Optional[str]) -> int:
        """
        根据标题标签计算层级：'x'->0, 'x.x'->1, 'x.x.x'->2, 依此类推。
        当 label 为空或不包含编号时，返回 0。
        """
        if not label:
            return 0
        parts = label.split(None, 1)
        number_part = parts[1] if len(parts) == 2 else parts[0]
        return number_part.count('.')

    for kind, item in _iter_block_items(doc):
        if kind == 'paragraph':
            text = item.text.strip()
            if not text:
                # 空段落忽略，但不影响状态
                continue
            p_idx = para_index_map.get(item._p)
            if p_idx is None:
                continue

            # 新标题开始：结束前一段，开启新 section
            if any(pat.match(text) for pat in TITLE_PATTERNS):
                if current_section:
                    content.append(current_section)
                label = extract_title_label(text)
                # 从 label 中解析编号部分 x.y.z（兼容字母与数字）
                section = None
                if label:
                    # 取最小编号部分（与 compute_title_level 逻辑一致）
                    parts = label.split(None, 1)
                    section = (parts[1] if len(parts) == 2 else parts[0]).strip()
                current_section = TableItem(
                    label=label,
                    section=section,
                    title=text,
                    level=compute_title_level(label),
                    title_indices=[p_idx]
                )
                collecting_title = True
                # 新标题意味着上一表格的脚注结束
                has_active_table = False
                continue

            # 非标题段落
            if current_section:
                if collecting_title:
                    # 如果遇到可能是脚注的文本，则不再视为多行标题，转入脚注收集
                    if _is_probable_footnote(text, item.style.name):
                        collecting_title = False
                    else:
                        # 可能的多行标题（简短、非 Heading、非制表符开头）
                        if len(text) < 100 and not text.startswith('\t') and not item.style.name.startswith('Heading'):
                            current_section.title_indices.append(p_idx)
                            # 不更新 title_label（以第一行标题为准）
                            continue
                        # 结束标题收集阶段
                        collecting_title = False

                # 若存在当前表格块，则将该段落视为其脚注的一部分
                if has_active_table and current_section.table_index is not None:
                    current_section.add_footnote_index(p_idx)
                    current_section.append_footnote_text(text)
                    continue

                # 无表格场景：对紧随标题后的脚注文本进行收集
                if current_section.table_index is None and _is_probable_footnote(text, item.style.name):
                    current_section.add_footnote_index(p_idx)
                    current_section.append_footnote_text(text)
                    continue

        elif kind == 'table':
            tbl_idx = tbl_index_map.get(item._tbl)
            if tbl_idx is None:
                continue
            # 标记已出现表格，并记录当前 section 的表格索引
            if current_section:
                current_section.set_table(tbl_idx)
                has_active_table = True
                collecting_title = False

    # 收尾：将最后一个 section 放入内容
    if current_section:
        content.append(current_section)

    return content