"""
PDF 文本切分实现

复刻 doc_worker 的完整切分逻辑：
1. 跳过目录页，只在正文中定位章节标题
2. 按最低目录层级进行切分，形成章节块
3. 对超过最大字符数的块按段落-句子进行再次切分，保持语义完整性
4. 支持层级路径构建和子标题查找
"""

from __future__ import annotations

from typing import Any, Dict, List

from ..config.provider import default_config_provider
from ..interfaces import TextSplitter
from ..utils.title_matcher import TitleMatcher
from ..utils.text_split_support import HierarchicalChunkMixin


class PdfTextSplitter(TextSplitter, HierarchicalChunkMixin):
    """按目录层级对 PDF 正文进行智能分块的实现（复刻 doc_worker 逻辑）。"""

    def __init__(self) -> None:
        self._cfg = default_config_provider
        self._title_matcher = TitleMatcher()

    def split_by_hierarchy(
        self,
        classification_items: List[Dict[str, Any]],
        pages_content: List[Dict[str, Any]],
        toc_info: Dict[str, Any],
        target_level: int,
        max_chunk_size: int,
        min_chunk_size: int,
    ) -> List[Dict[str, Any]]:
        """
        按目录层级和字符数智能切分文本
        
        新的分块逻辑：
        1. 跳过目录页，按目录项定位到指定层级的正文标题
        2. 在指定层级正文标题所属的正文块中，先按目录项的最低层级子标题进行分块
        3. 对每个块按字符数判断：
           - 超过max_chunk_size的进行句子级分割（保持语义尽量完整）
        """
        toc_pages = toc_info.get("toc_pages", []) or []
        all_toc_items = toc_info.get("toc_items", [])
        
        # 使用完整全文
        full_text = "".join(p.get("text", "") for p in pages_content)

        # 步骤1: 在正文中定位已分类的标题（跳过目录页）
        located = self._title_matcher.find_title_positions(
            classification_items, full_text, pages_content, toc_pages
        )
        
        # 只保留成功定位的标题
        found_titles = [t for t in located if t["found"]]
        if not found_titles:
            # Fallback: 如果未找到标题但有正文内容，将全文作为一个块
            if full_text.strip():
                print("  警告: 未找到标题，将全文作为一个块处理")
                return self._finalize_chunk_ids([{
                    "file_name": "",
                    "chunk_id": "temp_id",
                    "section_label": "正文",
                    "project_plan_type": "other",
                    "chapter_classification": "other",
                    "element_tag": {
                        "chunk_id": "temp_id",
                        "page": 1,
                        "serial_number": "1",
                    },
                    "review_chunk_content": full_text,
                }])
            
            print(f"  错误: 未能在正文中定位任何标题")
            return []

        print(f"  成功定位 {len(found_titles)}/{len(classification_items)} 个标题")
        
        # 按位置排序
        found_titles.sort(key=lambda x: x["position"])

        # 步骤2: 构建一级目录标题到分类信息的映射
        chapter_classification_map: Dict[str, Dict[str, Any]] = {}
        for item in classification_items:
            if item.get("level") == 1:
                chapter_title = item.get("title", "")
                chapter_classification_map[chapter_title] = {
                    "category": item.get("category", ""),
                    "category_code": item.get("category_code", "other"),
                    "page": item.get("page", ""),
                    "level": item.get("level", 1),
                }

        # 步骤3: 为每个找到的标题构建完整的层级路径
        for title_info in found_titles:
            hierarchy_path = self._build_hierarchy_path(
                title_info["title"], all_toc_items, target_level
            )
            title_info["hierarchy_path"] = hierarchy_path

        # 步骤4: 按目录层级处理每个标题块
        all_chunks: List[Dict[str, Any]] = []

        # 建立已定位标题的快速查找映射，用于后续 TOC 边界保护
        found_titles_map = {t["title"]: t["position"] for t in found_titles}

        for i, title_info in enumerate(found_titles):
            start_pos = title_info["position"]

            # 基础边界：下一个已定位的同级标题
            if i + 1 < len(found_titles):
                end_pos = found_titles[i + 1]["position"]
            else:
                end_pos = len(full_text)

            # TOC 边界保护：防止因标题定位错误导致的跨章节合并。
            # 问题场景（用户原话描述）：
            # "当时的规则是两个标题之间的内容。但如果说最后一个标题跨章节了，
            #  它就缺失了，缺失就会把下个章节的第一个标题，然后合并到最后上一个
            #  章节的最后一个节里面。"
            # 典型表现：第十章标题被错误定位到目录页（page 6），导致真正的第十章
            # 没被识别，第九章最后一个二级标题 content_block 的 end_pos 被延长到
            # len(full_text)，将第十章的"计算书"、"相关施工图纸"等全部内容吞进
            # doc_chunk_第九章->五_1。
            toc_boundary = self._get_toc_boundary_position(
                title_info["title"], all_toc_items, target_level, found_titles_map, full_text
            )
            if toc_boundary is not None and toc_boundary > start_pos:
                end_pos = min(end_pos, toc_boundary)

            # 提取正文块
            content_block = full_text[start_pos:end_pos]

            # 在正文块中查找子标题（按最低层级切分）
            sub_chunks = self._split_by_sub_titles(
                content_block,
                all_toc_items,
                title_info,
                target_level,
                max_chunk_size,
                min_chunk_size,
            )

            # 为每个子块添加元数据
            for j, sub_chunk in enumerate(sub_chunks, 1):
                chunk_data = self._build_chunk_metadata(
                    sub_chunk, title_info, start_pos, pages_content, i, j, chapter_classification_map
                )
                all_chunks.append(chunk_data)

        # 步骤5: 生成最终的chunk_id和serial_number
        final_chunks = self._finalize_chunk_ids(all_chunks)

        print(f"  完成切分: {len(final_chunks)} 个块")

        return final_chunks

    def _get_toc_boundary_position(
        self,
        title: str,
        all_toc_items: List[Dict[str, Any]],
        target_level: int,
        found_titles_map: Dict[str, int],
        full_text: str,
    ) -> int | None:
        """
        在 all_toc_items 中找到当前标题的下一个兄弟/更高级标题，
        并返回其在正文中的边界位置，防止 content_block 跨章节合并。
        """
        current_idx = -1
        for idx, item in enumerate(all_toc_items):
            if item.get("title") == title and item.get("level", target_level) == target_level:
                current_idx = idx
                break

        if current_idx < 0:
            return None

        for idx in range(current_idx + 1, len(all_toc_items)):
            item = all_toc_items[idx]
            if item.get("level", 1) <= target_level:
                boundary_title = item["title"]
                # 优先使用已定位的位置
                if boundary_title in found_titles_map:
                    return found_titles_map[boundary_title]
                # 回退：尝试在正文中直接定位
                if full_text and self._title_matcher:
                    pos = self._title_matcher._find_title_in_text(
                        boundary_title,
                        full_text,
                        float(self._cfg.get("text_splitting.fuzzy_threshold", 0.8)),
                    )
                    if pos >= 0:
                        return pos
                return None

        return None

    def _split_by_sub_titles(
        self,
        content_block: str,
        all_toc_items: List[Dict[str, Any]],
        parent_title_info: Dict[str, Any],
        target_level: int,
        max_chunk_size: int,
        min_chunk_size: int,
    ) -> List[Dict[str, Any]]:
        """
        在正文块中按子标题进行切分（按照toc_items的顺序和层级关系）
        
        核心逻辑：
        1. 查找所有层级的子标题（不限于直接子标题）
        2. 按位置排序后，两个相邻子标题之间的内容作为一个块
        3. 只有当块超过 max_chunk_size 时才按句子切分
        """
        # 找到父标题在toc_items中的位置
        parent_title = parent_title_info["title"]
        parent_idx = -1
        parent_level = target_level
        
        for idx, toc_item in enumerate(all_toc_items):
            if toc_item["title"] == parent_title:
                parent_idx = idx
                parent_level = toc_item.get("level", target_level)
                break

        if parent_idx < 0:
            # 如果找不到父标题，将整个正文块作为一个块
            if len(content_block) > max_chunk_size:
                return self._split_large_chunk(content_block, max_chunk_size, parent_title, [])
            else:
                return [
                    {
                        "content": content_block,
                        "relative_start": 0,
                        "sub_title": "",
                        "hierarchy_path": parent_title_info.get("hierarchy_path", [parent_title]),
                    }
                ]

        # 找到下一个同级或更高级标题的位置（确定父标题的范围）
        next_sibling_idx = len(all_toc_items)
        for idx in range(parent_idx + 1, len(all_toc_items)):
            item = all_toc_items[idx]
            if item.get("level", 1) <= parent_level:
                next_sibling_idx = idx
                break

        # 查找所有子标题（所有 level > parent_level 的标题）
        # 这是关键：不限于直接子标题，而是所有更深层级的标题
        all_sub_titles = []
        fuzzy_threshold = float(self._cfg.get("text_splitting.fuzzy_threshold", 0.8))

        for idx in range(parent_idx + 1, next_sibling_idx):
            toc_item = all_toc_items[idx]
            item_level = toc_item.get("level", 1)
            
            # 查找所有更深层级的子标题
            if item_level > parent_level:
                # 在正文块中查找这个子标题
                pos = self._find_title_in_block(
                    toc_item["title"], content_block, fuzzy_threshold
                )
                if pos >= 0:
                    all_sub_titles.append(
                        {
                            "title": toc_item["title"],
                            "level": toc_item["level"],
                            "position": pos,
                            "toc_index": idx,
                            "toc_item": toc_item,
                        }
                    )

        # 按位置排序
        all_sub_titles.sort(key=lambda x: x["position"])

        # 如果没有找到任何子标题，将整个正文块作为一个块
        if not all_sub_titles:
            if len(content_block) > max_chunk_size:
                return self._split_large_chunk(
                    content_block, max_chunk_size, parent_title, 
                    parent_title_info.get("hierarchy_path", [parent_title])
                )
            else:
                return [
                    {
                        "content": content_block,
                        "relative_start": 0,
                        "sub_title": "",
                        "hierarchy_path": parent_title_info.get("hierarchy_path", [parent_title]),
                    }
                ]

        # 找到直接子标题（parent_level + 1）和所有更深层级的标题
        direct_child_level = parent_level + 1
        direct_child_titles = [sub for sub in all_sub_titles if sub["level"] == direct_child_level]
        
        # 找到最低层级（用于判断哪些是最底层的标题）
        max_level = max(sub["level"] for sub in all_sub_titles) if all_sub_titles else parent_level

        # 如果没有直接子标题，但有更深层级的标题，使用最低层级标题切分（保持向后兼容）
        if not direct_child_titles and all_sub_titles:
            lowest_level_titles = [sub for sub in all_sub_titles if sub["level"] == max_level]
            direct_child_titles = lowest_level_titles

        # 按直接子标题切分（如果存在）
        chunks = []
        if direct_child_titles:
            for i, sub_title in enumerate(direct_child_titles):
                start_pos = sub_title["position"]

                # 确定结束位置（下一个同级或更高级标题的位置）
                # 在 all_sub_titles 中查找下一个位置大于当前标题，且 level <= direct_child_level 的标题
                end_pos = len(content_block)
                for next_sub in all_sub_titles:
                    if next_sub["position"] > start_pos and next_sub["level"] <= direct_child_level:
                        end_pos = next_sub["position"]
                        break

                chunk_content = content_block[start_pos:end_pos]

                # 检查子标题是否有实际正文内容
                title_len = len(sub_title["title"])
                content_after_title = chunk_content[title_len:].strip()

                if not content_after_title or len(content_after_title) < 10:
                    continue

                # 构建层级路径
                hierarchy_path = self._build_hierarchy_path_for_subtitle(
                    sub_title["toc_item"], all_toc_items, parent_title_info
                )

                # 只有当块超过 max_chunk_size 时才按句子切分
                if len(chunk_content) > max_chunk_size:
                    split_chunks = self._split_large_chunk(
                        chunk_content, max_chunk_size, sub_title["title"], hierarchy_path
                    )
                    for split_chunk in split_chunks:
                        split_chunk["relative_start"] = start_pos + split_chunk["relative_start"]
                        split_chunk["sub_title"] = sub_title["title"]
                        if "hierarchy_path" not in split_chunk:
                            split_chunk["hierarchy_path"] = hierarchy_path
                        chunks.append(split_chunk)
                else:
                    # 直接作为一个块
                    chunks.append(
                        {
                            "content": chunk_content,
                            "relative_start": start_pos,
                            "sub_title": sub_title["title"],
                            "hierarchy_path": hierarchy_path,
                        }
                    )

        # 如果所有子标题都没有正文内容，返回整个正文块
        if not chunks:
            if len(content_block) > max_chunk_size:
                return self._split_large_chunk(
                    content_block, max_chunk_size, parent_title,
                    parent_title_info.get("hierarchy_path", [parent_title])
                )
            else:
                return [
                    {
                        "content": content_block,
                        "relative_start": 0,
                        "sub_title": "",
                        "hierarchy_path": parent_title_info.get("hierarchy_path", [parent_title]),
                    }
                ]

        return chunks

    def _find_title_in_block(self, title: str, block: str, fuzzy_threshold: float) -> int:
        """在文本块中查找标题位置（简化版）"""
        # 直接使用 TitleMatcher 的方法
        return self._title_matcher._find_title_in_text(title, block, fuzzy_threshold)


