#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
脚本理解Agent

功能: 脚本理解与分析（创作者视角）
- step1: Section划分 - 分析已有帖子的分段结构，理解创作者是如何组织内容的（树状结构输出）
- step2: 元素列表生成 - 从已有帖子内容中提取元素列表（树状结构输出）
"""

from typing import List
from src.components.agents.base import BaseLLMAgent
from src.utils.logger import get_logger
from src.utils.llm_invoker import LLMInvoker

logger = get_logger(__name__)


class ScriptUnderstandingAgent(BaseLLMAgent):
    """脚本理解Agent - 分析已有帖子的脚本结构
    """

    def __init__(
        self,
        name: str = "script_understanding_agent",
        description: str = "脚本理解Agent - 分析已有帖子的脚本结构（创作者视角）",
        model_provider: str = "google_genai",
        temperature: float = 0.1,
        max_tokens: int = 40960
    ):
        """初始化脚本理解Agent
        """
        system_prompt = self._build_system_prompt()
        super().__init__(
            name=name,
            description=description,
            model_provider=model_provider,
            system_prompt=system_prompt,
            temperature=temperature,
            max_tokens=max_tokens
        )

    def _build_system_prompt(self) -> str:
        """构建系统提示词"""
        return """你是脚本结构分析专家，擅长从创作者视角理解内容的分段结构。

# 核心能力
1. 结构识别：分析已有帖子内容，识别创作者的分段逻辑
2. 树状输出：通过children字段表示层级关系，不使用parent_id

# 工作原则
- step1（Section划分）：识别创作者如何分段组织内容（树状结构）
- step2（元素提取）：提取具体元素列表（树状结构）"""

    def step1(self, text_data: dict, images: List[str], topic_description: dict, content_weight: dict = None) -> dict:
        """第一步：Section划分
        分析已有帖子的内容，识别创作者是如何分段组织内容的
        """
        if not self.is_initialized:
            self.initialize()

        logger.info("【Step1】Section划分 - 分析帖子的分段结构")

        # 提取权重信息
        weight_info = ""
        strategy_guidance = ""
        if content_weight:
            image_weight = content_weight.get("图片权重", 5)
            text_weight = content_weight.get("文字权重", 5)
            primary_source = content_weight.get("主要信息源", "both")
            content_relationship = content_weight.get("图文关系", "相关")

            weight_info = f"""
# 图文权重信息

- 图片权重: {image_weight}/10
- 文字权重: {text_weight}/10
- 主要信息源: {primary_source}
- 图文关系: {content_relationship}
"""

            # 根据权重和关系确定策略指导
            weight_diff = image_weight - text_weight

            # 确定策略类型和验证要求
            if weight_diff >= 3:
                strategy_type = "以图为主"
                strategy_guidance = """
## 图文策略: 以图为主

**核心原则**: 用图划分段落,图与图之间有顺序,文可以乱序对应到段落

**Section切分要点**:
- 以图片序列为核心主线进行划分
- **图片编号必须保持严格连续性**
- 文字可以灵活对应到图片
- 关注图片之间的关系、图片内容的语义分段、叙事逻辑
"""
                validation_guidance = """
**验证内容**：
1. ✅ **图片连续性验证**（核心验证）
   - 检查每个Section内的图片编号是否严格连续
   - 方法：逐个检查图片编号，确保无跳跃

2. ✅ **图片语义关系验证**
   - 同一Section内的图片是否有语义关联
   - 图片之间的过渡是否自然合理

3. ❌ **不验证文字顺序**
   - 文字可以灵活对应到图片

**禁止行为**：
- ❌ 跨段落跳跃式引用图片
- ❌ 打乱图片的原始顺序

**验证流程**：
- Step1: 提取每个Section的图片编号列表
- Step2: 检查图片编号是否连续
- Step3: 如发现跳跃，重新调整Section划分
"""
            elif weight_diff <= -3:
                strategy_type = "以文为主"
                strategy_guidance = """
## 图文策略: 以文为主

**核心原则**: 用文划分段落,文与文之间有顺序,图可以乱序对应到段落

**Section切分要点**:
- 以文字段落为核心主线进行划分
- **文字必须保持原始顺序**
- 图片可以灵活对应到文字
- 关注文字的话题转换、逻辑推进、情绪变化
"""
                validation_guidance = """
**验证内容**：
1. ✅ **文字顺序验证**（核心验证）
   - 检查每个Section内的文字是否保持原始顺序
   - 段落之间的文字流转是否自然连贯

2. ✅ **文字逻辑性验证**
   - 同一Section内的文字是否有逻辑关联
   - 话题转换是否合理

3. ❌ **不验证图片顺序**
   - 图片可以灵活对应到文字

**禁止行为**：
- ❌ 打乱文字的原始顺序
- ❌ 跨段落错乱文字位置

**验证流程**：
- Step1: 提取每个Section的文字内容
- Step2: 按原文顺序检查文字是否连贯
- Step3: 如发现乱序，重新调整Section划分
"""
            else:
                # 策略3/4: 图文同权
                if content_relationship == "相关":
                    strategy_type = "图文同权-图文相关"
                    strategy_guidance = """
## 图文策略: 图文同权-图文相关

**核心原则**: 根据消费者吸引力,判断图/文为主,另一个为辅

**Section切分要点**:
- 综合图文信息进行Section划分
- 根据图片排版质量、文字表达清晰度等判断主导维度
- 保持图文的对应关系,相互解释
- 关注图文相互补充的部分、哪个维度的表达更完整
"""
                    validation_guidance = """
**前置步骤**：
- 先判断主导维度（图为主 or 文为主）
- 明确记录判断依据

**验证内容**：
1. ✅ **主导维度顺序验证**（核心验证）
   - 如果图为主：验证图片序列的严格连续性
   - 如果文为主：验证文字段落的逻辑性

2. ✅ **图文对应关系验证**
   - 检查图文之间的对应关系是否合理
   - 图文是否相互解释、相互补充

3. ✅ **辅助维度合理性验证**
   - 辅助维度是否支撑主导维度

**禁止行为**：
- ❌ 主导维度出现顺序跳跃或错乱
- ❌ 图文对应关系混乱

**验证流程**：
- Step1: 判断并记录主导维度
- Step2: 针对主导维度执行严格顺序验证
- Step3: 验证图文对应关系
- Step4: 如发现问题，重新调整Section划分
"""
                else:
                    strategy_type = "图文同权-图文不相关"
                    strategy_guidance = """
## 图文策略: 图文同权-图文不相关

**核心原则**: 根据创作者目的,判断只关注图还是只关注文

**Section切分要点**:
- 明确选择关注图或关注文,不能同时关注
- 分析创作者的主要表达意图
- 选定维度后,完全基于该维度进行Section划分
- 关注创作者想重点传达什么、解构的目标是什么
"""
                    validation_guidance = """
**前置步骤**：
- 先判断选择关注的维度（图 or 文）
- 明确记录选择依据

**验证内容**：
1. ✅ **选定维度顺序验证**（核心验证）
   - 如果关注图：验证图片序列的严格连续性
   - 如果关注文：验证文字段落的逻辑性

2. ✅ **单一维度一致性验证**
   - 确认所有Section都基于选定的单一维度划分

3. ❌ **不验证未选择的维度**
   - 未选择的维度可以完全忽略

**禁止行为**：
- ❌ 选定维度出现顺序跳跃或错乱
- ❌ 同时关注图和文进行Section划分

**验证流程**：
- Step1: 判断并记录关注的维度
- Step2: 针对选定维度执行严格顺序验证
- Step3: 确认未使用另一维度进行划分
- Step4: 如发现问题，重新调整Section划分
"""

            logger.info(f"使用图文策略: {strategy_type} - 图片:{image_weight}, 文字:{text_weight}, 关系:{content_relationship}")
        else:
            strategy_type = "未指定"
            validation_guidance = ""
            logger.info("未提供图文权重信息")

        # 构建帖子内容
        post_content_parts = []
        if text_data.get("title"):
            post_content_parts.append(f"标题: {text_data['title']}")
        if text_data.get("body"):
            post_content_parts.append(f"正文: {text_data['body']}")

        post_content = "\n".join(post_content_parts) if post_content_parts else "无文本信息"

        # 构建选题描述文本
        topic_parts = []
        if topic_description.get("主题"):
            topic_parts.append(f"主题: {topic_description['主题']}")
        if topic_description.get("描述"):
            topic_parts.append(f"描述: {topic_description['描述']}")
        topic_text = "\n".join(topic_parts) if topic_parts else "无选题描述"

        # 构建prompt
        prompt = f"""{weight_info}
{strategy_guidance}

# 帖子内容

{post_content}

# 选题描述

{topic_text}

# 任务

从**创作者视角**分析这个已有帖子是如何组织内容的。

## Section切分流程

**第一步：应用图文策略**
根据上述图文策略指导,确定Section切分的主导维度和具体方法。

**第二步：识别主题显著变化位置**
扫描全部内容,识别**主题发生显著变化**的位置:

- **判断标准**:
  * 语义跃迁: 讨论对象发生根本性改变
  * 逻辑转换: 从"是什么"转向"为什么"或"怎么办"
  * 功能变化: 从"问题陈述"转向"解决方案"

- **划分原则**:
  * 避免过度细分(每个小变化都成为顶层段落)
  * 避免过度粗放(将所有内容合并为1个顶层段落)
  * 以"主题板块"而非"内容单元"为划分粒度

**第三步：初步划分**
- 按照策略要点确定核心驱动
- 基于主题显著变化位置进行划分
- 支持主Section和子Section的层级结构

**第四步：顺序验证与反思**
{validation_guidance}

## 层级要求

**段落必须至少保留2层结构**：
1. **第1层（抽象层）**：从具象中聚合出的共性维度
2. **第2层（具象层）**：具体的内容细节

**层级关系说明**：
- 抽象层是对多个具象内容的归纳和提炼
- 具象层是抽象层的具体展开
- 每个抽象层下必须有至少1个具象层子项

## Section字段

- 描述: 段落描述（共性维度名称；具体内容概括）
- 内容范围: **列表格式，包含具体内容**
  - 格式：["图X","正文-XXXX"]
  - 要求：必须包含具体的图片编号或文字原文片段
- 推理依据: 为什么这样划分
- 子项: 子Section列表（树状结构）

# 输出（JSON）- 树状结构

{{
  "内容品类": "内容品类",
  "段落列表": [
    {{
      "描述": "共性维度名称",
      "内容范围": ["图1", "图2", "正文——具体文字内容片段"],
      "推理依据": "为什么这样划分这个抽象层",
      "子项": [
        {{
          "描述": "具体内容概括",
          "内容范围": ["图1", "正文——具体文字内容片段"],
          "推理依据": "这个具象内容如何支撑上层抽象",
          "子项": []
        }}
      ]
    }}
  ]
}}"""

        # 构建多模态消息
        message_content = [{"type": "text", "text": prompt}]

        # 添加图片
        if images:
            for idx, image_url in enumerate(images, 1):
                message_content.append({"type": "text", "text": f"[图片{idx}]"})
                message_content.append({
                    "type": "image_url",
                    "image_url": {"url": image_url}
                })

        messages = [
            {"role": "system", "content": self.system_prompt},
            {"role": "user", "content": message_content}
        ]

        # 调用LLM
        result = LLMInvoker.safe_invoke(
            self,
            "Section划分",
            messages,
            fallback={"内容品类": "未知品类", "段落列表": []}
        )

        # 只打印JSON结构
        import json
        logger.info(f"Step1结果:\n{json.dumps(result, ensure_ascii=False, indent=2)}")

        return result

    def process(self, state: dict) -> dict:
        """处理state，执行完整的脚本理解流程（step1 + step2）

        从state中提取所需数据，执行step1和step2，并返回结果

        Args:
            state: 工作流状态，包含:
                - text: 文本数据 {title, body}
                - images: 图片URL列表
                - topic_selection_understanding: 选题理解结果
                - content_weight: 图文权重信息

        Returns:
            dict: 包含script_understanding结果
        """
        logger.info("=== 开始脚本理解处理 ===")

        # 从state中提取数据
        text_data = state.get("text", {})
        images = state.get("images", [])

        # 提取选题描述（作为字典）
        topic_understanding = state.get("topic_selection_understanding", {})
        topic_description = {
            "主题": topic_understanding.get("主题", ""),
            "描述": topic_understanding.get("描述", "")
        }

        # 提取图文权重
        content_weight = state.get("content_weight", {})

        # 执行 step1 - Section划分
        logger.info("执行 Step1 - Section划分")
        sections_result = self.step1(
            text_data=text_data,
            images=images,
            topic_description=topic_description,
            content_weight=content_weight
        )
        sections = sections_result.get("段落列表", [])

        # 执行 step2 - 元素列表生成
        logger.info("执行 Step2 - 元素列表生成")
        elements_result = self.step2(
            text_data=text_data,
            images=images,
            topic_description=topic_description,
            content_weight=content_weight
        )

        # 组装最终结果
        script_understanding = {
            "内容品类": sections_result.get("内容品类", "未知品类"),
            "段落列表": sections,
            "元素列表": elements_result.get("元素列表", []),
            "图片列表": images  # 添加原始图片URL列表
        }

        # 只打印JSON结构
        import json
        logger.info(f"脚本理解最终结果:\n{json.dumps(script_understanding, ensure_ascii=False, indent=2)}")

        return {"脚本理解": script_understanding}

    def step2_1_identify_candidates(self, text_data: dict, images: List[str], topic_description: dict, content_weight: dict = None) -> dict:
        """Step2.1: 候选元素识别

        基于选题主题，识别与主题相关的候选元素（包括实质性和概念性元素）
        """
        if not self.is_initialized:
            self.initialize()

        logger.info("=" * 80)
        logger.info("【Step2.1】候选元素识别 - 基于选题主题识别候选元素（实质性+概念性）")
        logger.info("=" * 80)

        # 构建权重信息
        weight_info = self._build_weight_info(content_weight)

        # 构建内容
        post_content = self._build_post_content(text_data)
        topic_text = self._build_topic_text(topic_description)

        # 构建prompt
        prompt = f"""{weight_info}

# 帖子内容

{post_content}

# 选题描述

{topic_text}

# 任务

**基于选题主题作为关注锚点**，识别与主题相关或支撑主题的候选元素。

## 选题的作用

选题主题是识别的**关注锚点**：
- ✅ **直接相关**：与选题主题直接相关的核心元素
- ✅ **支撑主题**：对主题有支撑作用的辅助元素（即使不是核心，但能帮助理解主题）
- ✅ **间接相关**：与选题主题间接相关的背景元素
- ❌ **完全无关**：与选题主题没有任何关联的元素

## 识别原则

- **全面识别**：宁可多识别，不要漏掉与主题相关或支撑主题的元素
- **关系多样**：包括直接相关、支撑性、补充性、背景性等各种关系
- **宽松标准**：此阶段标准宽松，后续会进一步筛选
- **倾向保留**：不确定是否相关时，倾向于保留

## 识别标准：实质性元素 + 概念性元素

### 实质性元素（Substantial Elements）
**定义**：看得见、摸得着的具体物体
- ✅ 必须是名词形式
- ✅ 必须是可观察的具体事物
- ✅ 必须是**原子的名词**，关注的是类/ID
- ✅ 包括所有出现的物体，无论大小或重要性
- ❌ 不包括动作或状态
- ❌ 不包括情绪或氛围
- ❌ 不包括带修饰语的复合名词（修饰语应在后续分析中体现）

### 概念性元素（Conceptual Elements）
**定义**：虽然不是客观实体，但是对理解帖子至关重要的抽象概念
- ✅ 必须是名词形式的概念
- ✅ 虽然没有直接提及，但图片或文字隐含了这个概念
- ✅ 出现频率高或重要性强的描述性概念
- ✅ 对理解主题有显著帮助的抽象维度

**概念性元素的判断标准**：
- ✅ 这个概念是否帮助理解帖子的核心价值？
- ✅ 这个概念是否贯穿多个段落或图片？
- ✅ 这个概念是否隐含在视觉或文字表达中？
- ❌ 不是单纯的形容词或副词，必须能转化为名词概念

## 识别要求

- **全面浏览**：不要遗漏任何出现的元素（实质性+概念性）
- **原始记录**：此阶段不进行筛选，全部记录
- **原子名词**：使用最基础的名词形式，去除修饰语
- **显性+隐性**：既要识别显性出现的实体，也要识别隐性的概念

## 元素类型标注

每个元素需要标注类型：
- **实质性**：具体的客观物体
- **概念性**：抽象的概念维度

# 输出（JSON）

{{
  "候选元素列表": [
    {{
      "元素名称": "元素名称（原子名词）",
      "元素类型": "实质性/概念性",
      "出现位置": "该元素出现在哪些位置",
      "识别依据": "为什么识别这个元素（特别是概念性元素，需要说明隐含的依据）"
    }}
  ]
}}"""

        # 构建多模态消息
        message_content = [{"type": "text", "text": prompt}]
        if images:
            for idx, image_url in enumerate(images, 1):
                message_content.append({"type": "text", "text": f"[图片{idx}]"})
                message_content.append({
                    "type": "image_url",
                    "image_url": {"url": image_url}
                })

        messages = [
            {"role": "system", "content": self.system_prompt},
            {"role": "user", "content": message_content}
        ]

        # 调用LLM
        result = LLMInvoker.safe_invoke(
            self,
            "候选元素识别",
            messages,
            fallback={"候选元素列表": []}
        )

        # 只打印JSON结构
        import json
        logger.info(f"Step2.1结果:\n{json.dumps(result, ensure_ascii=False, indent=2)}")

        return result

    def step2_2_score_dimensions(self, candidates_result: dict, text_data: dict, images: List[str], topic_description: dict, content_weight: dict = None) -> dict:
        """Step2.2: 双维度评分（分视角分析）

        对每个候选元素进行双维度评分（每维度0-10分）
        - 先分视角：判断概念性维度 vs 实质性维度哪个更重
        - 优先处理权重更大的维度
        - 维度1: 主题支撑性 - 评估元素对选题展开和目的达成的支撑程度
        - 维度2: 多段落共性 - 评估元素的贯穿性和共性
        """
        if not self.is_initialized:
            self.initialize()

        logger.info("\n" + "=" * 80)
        logger.info("【Step2.2】双维度评分（分视角分析）")
        logger.info("=" * 80)

        candidates = candidates_result.get("候选元素列表", [])
        if not candidates:
            logger.warning("⚠️  没有候选元素可供评分")
            return {"评分结果": []}

        # 统计概念性和实质性元素数量
        conceptual_count = sum(1 for c in candidates if c.get("元素类型") == "概念性")
        substantial_count = sum(1 for c in candidates if c.get("元素类型") == "实质性")

        logger.info(f"输入: {len(candidates)} 个候选元素（概念性: {conceptual_count}, 实质性: {substantial_count}）")

        # 构建权重信息
        weight_info = self._build_weight_info(content_weight)

        # 构建内容
        post_content = self._build_post_content(text_data)
        topic_text = self._build_topic_text(topic_description)

        # 构建候选元素列表文本
        candidates_text = ""
        for c in candidates:
            candidates_text += f"\n- {c.get('元素名称', 'N/A')} ({c.get('元素类型', 'N/A')})"
            candidates_text += f"\n  出现位置: {c.get('出现位置', 'N/A')}"
            if c.get('识别依据'):
                candidates_text += f"\n  识别依据: {c.get('识别依据', 'N/A')}"

        # 构建prompt
        prompt = f"""{weight_info}

# 帖子内容

{post_content}

# 选题描述

{topic_text}

# 候选元素列表

{candidates_text}

# 任务

对每个候选元素进行**双维度评分**（每维度0-10分）。

## 分视角分析流程

**第一步：判断维度权重**
根据候选元素列表，判断概念性元素和实质性元素哪个更重：
- 如果概念性元素数量多或对主题更核心 → 概念性维度权重更大
- 如果实质性元素数量多或对主题更核心 → 实质性维度权重更大
- 如果两者相当 → 两个维度权重相同

**第二步：优先处理权重更大的维度**
- 先对权重更大的维度进行详细评分和分析
- 确保权重更大维度的元素得到充分评估
- 再处理另一个维度作为补充

**第三步：综合评分**
- 对所有元素进行双维度评分
- 权重更大的维度应有更多高分元素

## 两大评分维度

### 1. 主题支撑性（0-10分）
评估元素对选题展开和目的达成的支撑程度：
- **核心支撑（8-10分）**：元素是选题展开的核心支撑，直接承载创作目的
- **重要支撑（5-7分）**：元素为选题提供重要支撑，对目的达成有明显作用
- **辅助支撑（3-4分）**：元素为选题提供辅助性支撑
- **弱支撑（0-2分）**：元素与选题关联度低，对目的达成作用微弱

### 2. 多段落共性（0-10分）
评估元素在多个段落中的贯穿性和共性：
- **跨段落出现**：元素在多个段落/Section中出现
- **持续性存在**：元素在内容中的连续性呈现
- **代表性**：元素的典型性和普遍性，能否代表一类事物

**贯穿段落字段说明**：
- 格式：列表格式，包含该元素出现的具体内容
- 要求：必须包含具体的图片编号或文字原文片段

## 评分要求

- 客观评分：基于实际内容，不要主观臆测
- 严格打分：真正优秀的元素才能得高分
- 详细说明：每个维度都要给出评分依据
- 分视角优先：优先处理权重更大的维度

# 输出（JSON）

{{
  "维度判断": {{
    "概念性元素重要性": "高/中/低",
    "实质性元素重要性": "高/中/低",
    "优先维度": "概念性/实质性/均衡",
    "判断依据": "为什么这个维度更重要"
  }},
  "评分结果": [
    {{
      "元素名称": "元素名称",
      "元素类型": "概念性/实质性",
      "主题支撑性得分": 8,
      "主题支撑性依据": "评分依据说明（如何支撑选题展开和目的达成）",
      "多段落共性得分": 7,
      "多段落共性依据": "评分依据说明",
      "贯穿段落": ["图1", "图2", "正文——具体文字内容片段"]
    }}
  ]
}}"""

        # 构建多模态消息
        message_content = [{"type": "text", "text": prompt}]
        if images:
            for idx, image_url in enumerate(images, 1):
                message_content.append({"type": "text", "text": f"[图片{idx}]"})
                message_content.append({
                    "type": "image_url",
                    "image_url": {"url": image_url}
                })

        messages = [
            {"role": "system", "content": self.system_prompt},
            {"role": "user", "content": message_content}
        ]

        # 调用LLM
        result = LLMInvoker.safe_invoke(
            self,
            "双维度评分",
            messages,
            fallback={"评分结果": []}
        )

        # 只打印JSON结构
        import json
        logger.info(f"Step2.2结果:\n{json.dumps(result, ensure_ascii=False, indent=2)}")

        return result

    def step2_3_strict_filter(self, scored_result: dict, single_threshold: float = 6.0, weighted_threshold: float = 5.0) -> dict:
        """Step2.3: 严格筛选

        根据评分结果进行严格筛选，满足以下条件之一即可通过：
        1. 主题支撑性 > single_threshold
        2. 多段落共性 > single_threshold
        3. 加权得分 > weighted_threshold（加权：主题支撑性 * 0.5 + 多段落共性 * 0.5）

        Args:
            scored_result: 评分结果
            single_threshold: 单维度筛选阈值，默认为6.0（降低阈值，返回更多支撑主题的元素）
            weighted_threshold: 加权得分筛选阈值，默认为5.0（整体合格即可）
        """
        if not self.is_initialized:
            self.initialize()

        logger.info("\n" + "=" * 80)
        logger.info("【Step2.3】严格筛选")
        logger.info("=" * 80)
        logger.info("筛选规则（满足任一条件即通过）:")
        logger.info(f"  1. 主题支撑性 > {single_threshold} (单维度良好)")
        logger.info(f"  2. 多段落共性 > {single_threshold} (单维度良好)")
        logger.info(f"  3. 加权得分 > {weighted_threshold} (整体合格，权重：主题支撑性=0.5, 多段落共性=0.5)")
        logger.info(f"  注：阈值已从7降低到6，以返回更多支撑主题的元素")

        scored_elements = scored_result.get("评分结果", [])
        if not scored_elements:
            logger.warning("⚠️  没有评分结果可供筛选")
            return {"筛选结果": []}

        logger.info(f"\n输入: {len(scored_elements)} 个评分元素")
        logger.info("\n筛选过程:")

        filtered_elements = []

        for element in scored_elements:
            element_name = element.get("元素名称", "未知")

            # 提取分数
            theme_score = element.get("主题支撑性得分", 0)
            multi_para_score = element.get("多段落共性得分", 0)

            # 计算加权得分（主题支撑性和多段落共性各占50%）
            weighted_score = theme_score * 0.5 + multi_para_score * 0.5

            # 判断是否满足任一条件
            conditions_met = []

            if theme_score > single_threshold:
                conditions_met.append(f"主题支撑性={theme_score}")

            if multi_para_score > single_threshold:
                conditions_met.append(f"多段落共性={multi_para_score}")

            if weighted_score > weighted_threshold:
                conditions_met.append(f"加权得分={weighted_score:.1f}")

            # 如果满足任一条件，则通过筛选
            if conditions_met:
                logger.info(f"  ✓ {element_name}: 通过筛选 ({', '.join(conditions_met)})")
                filtered_elements.append(element)
            else:
                logger.info(f"  ✗ {element_name}: 未通过筛选 (主题支撑性={theme_score}, 多段={multi_para_score}, 加权={weighted_score:.1f})")

        # 只打印JSON结构
        import json
        logger.info(f"Step2.3结果:\n{json.dumps({'筛选结果': filtered_elements}, ensure_ascii=False, indent=2)}")

        return {"筛选结果": filtered_elements}

    def step2_4_deduplicate(self, filtered_result: dict, text_data: dict, images: List[str], topic_description: dict) -> dict:
        """Step2.4: 去重与整合

        合并同类元素，确保元素之间正交独立，生成树状结构
        - 非叶子节点：分类（抽象维度）
        - 叶子节点：具体元素
        """
        if not self.is_initialized:
            self.initialize()

        logger.info("\n" + "=" * 80)
        logger.info("【Step2.4】去重与整合")
        logger.info("=" * 80)

        filtered_elements = filtered_result.get("筛选结果", [])
        if not filtered_elements:
            logger.warning("⚠️  没有筛选结果可供去重")
            return {"元素列表": []}

        logger.info(f"输入: {len(filtered_elements)} 个筛选后的元素")

        # 构建内容
        post_content = self._build_post_content(text_data)
        topic_text = self._build_topic_text(topic_description)

        # 构建筛选元素文本
        elements_text = ""
        for elem in filtered_elements:
            elements_text += f"\n- {elem.get('元素名称', 'N/A')}"
            elements_text += f"\n  主题支撑性: {elem.get('主题支撑性得分', 0)}, 多段落共性: {elem.get('多段落共性得分', 0)}"
            elements_text += f"\n  贯穿段落: {elem.get('贯穿段落', 'N/A')}"

        # 构建prompt
        prompt = f"""# 帖子内容

{post_content}

# 选题描述

{topic_text}

# 筛选后的元素列表
{elements_text}

# 任务

对筛选后的元素进行**去重与整合**，生成最终的树状结构元素列表。

## 核心原则

**树状结构：非叶子节点为分类，叶子节点为具体元素**

- 非叶子节点：分类（抽象维度），可以包含子分类或具体元素
- 叶子节点：具体元素，是实际的元素
- 层级深度：根据实际内容灵活确定，不限定层数
- 分类节点通过"子项"字段包含下一层的分类或元素

## 去重规则

1. **合并同类**：将具有相同上位概念的元素归类到同一分类下
2. **正交独立**：同级节点之间必须互不重叠、相互独立

## 分类规则（使用元素本身 What 的维度）

**核心原则：分类基于元素本身的 What 维度（本质属性）**

- **What 维度**：回答"这是什么类型的东西"
- **分类来源**：从元素本身的本质属性中抽象出分类，基于"它们是什么"来分类
- **分类层级**：分类可以嵌套（分类下可以有子分类）
- **同级正交**：同一分类下的节点应该具有相同的 What 维度（上位概念）

**MECE分类视角框架（选择最适合的一个维度）**：
- **物理维度**：基于物理属性（形态、结构、状态等）
- **化学维度**：基于化学成分和组成
- **生物维度**：基于生物分类（动物、植物、微生物等）
- **功能维度**：基于客观功能分类（注意：是物体本身的功能属性，不是对其他事物的影响）

**分类禁止项**：
- ❌ 禁止从"对其他事物的影响/效果"角度分类
- ❌ 禁止从"目的/价值"角度分类
- ❌ 禁止使用抽象概念作为分类

**判断标准**：
分类名称应该能回答"XX是一种什么？"，而不是"XX对YY有什么作用？"或"XX导致什么结果？"

## 节点字段说明

### 分类节点（非叶子节点）
- **元素名称**：分类名称（基于 What 维度的抽象分类）
- **描述**：对这个分类的定义说明（回答"这是什么类型的东西"）
- **子项**：该分类下的子分类或具体元素列表

### 元素节点（叶子节点）
- **元素名称**：使用原子名词（最基础的名词形式，去除修饰语）
- **描述**：该元素在帖子中的定义，包含具体表现
- **上游支撑**：说明该元素如何支撑选题的展开和目的达成
- **贯穿段落**：该元素贯穿的内容范围，列表格式（如：["图1", "图2", "正文——具体文字内容片段"]）
- **主题支撑性得分**：0-10分
- **多段落共性得分**：0-10分
- **推理依据**：为什么这是核心元素
- **子项**：[]（叶子节点，必须为空列表）

## 输出要求

- 元素数量：宁少勿多，只保留真正核心的元素
- 同级正交：同级节点之间必须相互独立
- 描述清晰：每个节点都要有清晰的定义
- 支撑明确：每个元素都要说明如何支撑选题
- 层级灵活：根据实际内容确定树的深度

# 输出（JSON）- 树状结构

{{
  "元素列表": [
    {{
      "元素名称": "一级分类名称",
      "描述": "对这个分类的定义说明",
      "子项": [
        {{
          "元素名称": "二级分类名称（可选）",
          "描述": "对这个子分类的定义说明",
          "子项": [
            {{
              "元素名称": "具体元素名称（原子名词）",
              "描述": "该元素在帖子中的定义，包含具体表现",
              "上游支撑": "该元素如何支撑选题的展开和目的达成",
              "贯穿段落": ["图1", "图2", "正文——具体文字内容片段"],
              "主题支撑性得分": 8,
              "多段落共性得分": 9,
              "推理依据": "为什么这是核心元素（如何满足双维度要求）",
              "子项": []
            }}
          ]
        }},
        {{
          "元素名称": "具体元素名称（原子名词）",
          "描述": "该元素在帖子中的定义，包含具体表现",
          "上游支撑": "该元素如何支撑选题的展开和目的达成",
          "贯穿段落": ["图3", "正文——具体文字内容片段"],
          "主题支撑性得分": 7,
          "多段落共性得分": 8,
          "推理依据": "为什么这是核心元素",
          "子项": []
        }}
      ]
    }}
  ]
}}

**注意**：
- 如果分类下直接是具体元素，则元素是该分类的直接子项
- 如果需要更细的分类，可以在分类下再创建子分类
- 具体元素（叶子节点）必须包含完整的元素字段（描述、上游支撑、评分等）
- 分类节点（非叶子）只需要包含名称、描述和子项"""

        # 构建多模态消息
        message_content = [{"type": "text", "text": prompt}]
        if images:
            for idx, image_url in enumerate(images, 1):
                message_content.append({"type": "text", "text": f"[图片{idx}]"})
                message_content.append({
                    "type": "image_url",
                    "image_url": {"url": image_url}
                })

        messages = [
            {"role": "system", "content": self.system_prompt},
            {"role": "user", "content": message_content}
        ]

        # 调用LLM
        result = LLMInvoker.safe_invoke(
            self,
            "去重与整合",
            messages,
            fallback={"元素列表": []}
        )

        # 只打印JSON结构
        import json
        logger.info(f"Step2.4结果:\n{json.dumps(result, ensure_ascii=False, indent=2)}")

        return result

    def step2(self, text_data: dict, images: List[str], topic_description: dict, content_weight: dict = None) -> dict:
        """第二步：元素 - 提取与主题相关的核心元素

        执行完整的4步筛选流程：
        1. 候选元素识别
        2. 双维度评分（主题支撑性 + 多段落共性）
        3. 严格筛选（满足任一条件即可）
        4. 去重与整合
        """
        if not self.is_initialized:
            self.initialize()

        logger.info("\n" + "█" * 80)
        logger.info("█" + " " * 78 + "█")
        logger.info("█" + " " * 25 + "【Step2】元素提取 - 四步筛选流程" + " " * 23 + "█")
        logger.info("█" + " " * 78 + "█")
        logger.info("█" * 80)

        # Step 2.1: 候选元素识别
        logger.info("\n▶ 开始执行 Step 2.1: 候选元素识别")
        candidates_result = self.step2_1_identify_candidates(
            text_data, images, topic_description, content_weight
        )

        # Step 2.2: 双维度评分
        logger.info("\n▶ 开始执行 Step 2.2: 双维度评分（主题支撑性 + 多段落共性）")
        scored_result = self.step2_2_score_dimensions(
            candidates_result, text_data, images, topic_description, content_weight
        )

        # Step 2.3: 严格筛选
        logger.info("\n▶ 开始执行 Step 2.3: 严格筛选（满足任一条件即可）")
        filtered_result = self.step2_3_strict_filter(scored_result)

        # Step 2.4: 去重与整合
        logger.info("\n▶ 开始执行 Step 2.4: 去重与整合")
        final_result = self.step2_4_deduplicate(
            filtered_result, text_data, images, topic_description
        )

        # 只打印JSON结构
        import json
        logger.info(f"Step2最终结果:\n{json.dumps(final_result, ensure_ascii=False, indent=2)}")

        return final_result

    def _build_weight_info(self, content_weight: dict = None) -> str:
        """构建权重信息文本"""
        if not content_weight:
            return ""

        image_weight = content_weight.get("图片权重", 5)
        text_weight = content_weight.get("文字权重", 5)
        primary_source = content_weight.get("主要信息源", "both")

        return f"""# 图文权重信息

- 图片权重: {image_weight}/10
- 文字权重: {text_weight}/10
- 主要信息源: {primary_source}

**元素提取时需要考虑权重**：
- 如果图片权重高，元素提取应更多关注图片中的视觉元素
- 如果文字权重高，元素提取应更多关注文字描述的概念和对象
- 如果权重相近，需要综合图文信息提取元素
"""

    def _build_post_content(self, text_data: dict) -> str:
        """构建帖子内容文本"""
        post_content_parts = []
        if text_data.get("title"):
            post_content_parts.append(f"标题: {text_data['title']}")
        if text_data.get("body"):
            post_content_parts.append(f"正文: {text_data['body']}")

        return "\n".join(post_content_parts) if post_content_parts else "无文本信息"

    def _build_topic_text(self, topic_description: dict) -> str:
        """构建选题描述文本"""
        topic_parts = []
        if topic_description.get("主题"):
            topic_parts.append(f"主题: {topic_description['主题']}")
        if topic_description.get("描述"):
            topic_parts.append(f"描述: {topic_description['描述']}")

        return "\n".join(topic_parts) if topic_parts else "无选题描述"

    def _build_messages(self, state: dict) -> List[dict]:
        """构建消息 - ScriptUnderstandingAgent 不使用此方法

        本 Agent 使用 step1 和 step2 方法直接构建消息
        """
        return []

    def _update_state(self, state: dict, response) -> dict:
        """更新状态 - ScriptUnderstandingAgent 不使用此方法

        本 Agent 使用 step1 和 step2 方法直接返回结果
        """
        return state
