#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
脚本元素提取Agent（完全重构版 - 8步流程）

功能: 从视频内容中提取核心实质点（元素 + 概念）
核心流程（8步，每步独立调用LLM）:
  1. 判断视频倾向 - 概念更重要 or 元素更重要
  2. 主视角提取 - 从更重要的视角提取
  3. 辅视角提取 - 从另一个视角提取，并与主视角去重
  4. 共性定量分析 - 元素和概念在段落中的出现分析（先统计基础数据）
  5. 多维度支撑评分 - 支撑灵感点、目的点、关键点、段落（基于共性数据评分）
  6. 筛选 - 基于评分筛选
  7. 去重 - 同类去重（概念与概念，元素与元素）
  8. 整合 - 为实质点添加分类标签（用方括号表示）
"""

import json
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, get_video_file_from_state

logger = get_logger(__name__)


class ScriptElementExtractionAgent(BaseLLMAgent):
    """脚本元素提取Agent - 提取视频的核心实质点（元素 + 概念）
    """

    def __init__(
        self,
        name: str = "script_element_extraction_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. 实质点识别：区分元素（具体实体）和概念（抽象名词）
3. 共性定量分析：定量分析实质点在段落中的出现情况（基础数据）
4. 多维度支撑性评分：评估对灵感点、目的点、关键点、段落的支撑（基于共性数据）
5. 分类整合：为实质点添加分类标签，确保同级正交

# 工作原则
- 名词化：所有实质点必须是名词形式
- 定量化：所有评分必须有客观依据和定量指标
- 同类去重：概念与概念去重，元素与元素去重，不做跨类型去重
- 结构化：使用方括号标记分类，确保同级正交"""

    def process(self, state: dict) -> dict:
        """处理state，执行完整的元素提取流程

        从state中提取所需数据，执行8步流程，并返回结果

        Args:
            state: 工作流状态，包含:
                - video: 视频URL
                - topic_selection_understanding: 选题理解结果
                - section_division: 段落划分结果
                - inspiration_points: 灵感点
                - purpose_points: 目的点
                - key_points: 关键点

        Returns:
            dict: 包含元素列表结果
        """
        logger.info("=== 开始脚本元素提取处理（完全重构版 - 8步流程） ===")

        # 从state中获取视频文件
        video_file = get_video_file_from_state(state)
        if not video_file:
            logger.error("无法从state中获取视频文件")
            return {
                "元素列表": [],
                "视频倾向判断": {}
            }

        # 提取选题描述
        topic_understanding = state.get("topic_selection_understanding", {})
        topic_description = {
            "主题": topic_understanding.get("主题", ""),
            "描述": topic_understanding.get("描述", "")
        }

        # 提取段落划分结果
        section_division = state.get("section_division", {})

        # 提取灵感点、目的点、关键点
        inspiration_points = state.get("inspiration_points", {})
        purpose_points = state.get("purpose_points", [])
        key_points = state.get("key_points", {})

        # 执行8步流程
        logger.info("执行完整的实质点提取流程（8步）")
        elements_result = self._execute_8_steps_pipeline(
            video_file=video_file,
            topic_description=topic_description,
            section_division=section_division,
            inspiration_points=inspiration_points,
            purpose_points=purpose_points,
            key_points=key_points
        )

        # 打印JSON结构
        logger.info(f"元素提取最终结果:\n{json.dumps(elements_result, ensure_ascii=False, indent=2)}")

        return {
            "元素列表": elements_result.get("元素列表", []),
            "视频倾向判断": elements_result.get("视频倾向判断", {})
        }

    def _execute_8_steps_pipeline(
        self,
        video_file,
        topic_description: dict,
        section_division: dict,
        inspiration_points: dict,
        purpose_points: list,
        key_points: dict
    ) -> dict:
        """执行完整的8步流程

        每一步都是独立的LLM调用
        """
        logger.info("\n" + "█" * 80)
        logger.info("█" + " " * 78 + "█")
        logger.info("█" + " " * 20 + "【实质点提取】8步流程（每步独立）" + " " * 21 + "█")
        logger.info("█" + " " * 78 + "█")
        logger.info("█" * 80)

        # Step 1: 判断视频倾向
        logger.info("\n▶ Step 1: 判断视频倾向（概念 or 元素更重要）")
        tendency_result = self._step1_judge_tendency(
            video_file, topic_description
        )

        # Step 2: 主视角提取
        logger.info("\n▶ Step 2: 主视角提取（从更重要的视角提取）")
        primary_result = self._step2_extract_primary(
            tendency_result, video_file, topic_description
        )

        # Step 3: 辅视角提取
        logger.info("\n▶ Step 3: 辅视角提取（从另一个视角提取，并与主视角去重）")
        secondary_result = self._step3_extract_secondary(
            tendency_result, primary_result, video_file, topic_description
        )

        # 合并主视角和辅视角的结果
        all_substantials = primary_result.get("实质点列表", []) + secondary_result.get("实质点列表", [])
        logger.info(f"合并后总计: {len(all_substantials)} 个实质点")

        # Step 4: 共性定量分析 + 段落支撑评分（合并）
        logger.info("\n▶ Step 4: 共性定量分析 + 段落支撑评分（合并）")
        analyzed_result = self._step4_quantitative_analysis(
            all_substantials,
            video_file,
            section_division
        )

        # Step 5: 多维度支撑评分（3个维度并行 - 基于共性数据评分）
        logger.info("\n▶ Step 5: 多维度支撑评分（3个维度并行 - 基于共性数据）")
        scored_result = self._step5_multi_dimensional_scoring(
            analyzed_result,
            video_file,
            inspiration_points,
            purpose_points,
            key_points
        )

        # Step 6: 筛选
        logger.info("\n▶ Step 6: 筛选核心实质点")
        filtered_result = self._step6_filter(scored_result)

        # Step 7: 去重（同类去重）
        logger.info("\n▶ Step 7: 去重（概念与概念，元素与元素）")
        deduplicated_result = self._step7_deduplicate(
            filtered_result
        )

        # Step 8: 整合
        logger.info("\n▶ Step 8: 整合（生成树状结构）")
        final_result = self._step8_integrate(
            deduplicated_result
        )

        # 在最终结果中添加 step1 的判断结果（这个是全局的）
        final_result["视频倾向判断"] = tendency_result

        return final_result

    def _step1_judge_tendency(
        self,
        video_file,
        topic_description: dict
    ) -> dict:
        """Step 1: 判断视频倾向

        判断对于这个视频来说，概念更重要还是元素更重要

        参数说明：
        - video_file: Gemini视频文件对象
        - topic_description: 选题描述，帮助理解视频主题和方向
        """
        logger.info("=" * 80)
        logger.info("【Step 1】判断视频倾向 - 概念 or 元素更重要")
        logger.info("=" * 80)

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

        # 构建prompt
        prompt = f"""# 选题描述

{topic_text}

# 任务

判断对于**这个视频**来说，**概念更重要**还是**元素更重要**。

## 定义

### 概念（Concept）
- 抽象的名词，不能是形式的

### 元素（Element）
- 具体的、客观存在的实体，视觉可见

## 判断标准

### 概念更重要
视频的核心价值在于**抽象的方法论、流程、原理**：
- 强调过程和方法
- 强调原理和知识传递
- 强调教学、指导、分享经验

### 元素更重要
视频的核心价值在于**具体的物品、实物展示**：
- 强调物品本身
- 强调视觉呈现和展示
- 强调具体实物的特征

## 判断流程

1. **分析视频核心价值**：这个视频的核心价值是什么？
2. **识别主要内容类型**：是方法论/流程/原理，还是物品/实物展示？
3. **评估抽象 vs 具体的比重**：哪个占主导地位？
4. **做出判断**：概念更重要 or 元素更重要

## 输出要求

- 必须明确给出判断结果
- 必须给出充分的理由
- 必须引用视频内容作为证据

# 输出（JSON）

{{
  "判断结果": "概念更重要/元素更重要",
  "核心价值": "这个视频的核心价值是什么",
  "主要内容类型": "方法论/流程/原理/物品展示/实物介绍等",
  "抽象vs具体比重": "抽象内容占X%，具体内容占Y%（估算）",
  "判断理由": "为什么做出这个判断（引用视频内容作为证据）",
  "支撑证据": ["证据1：具体引用", "证据2：具体引用"]
}}"""

        # 调用LLM（使用视频分析接口）
        result = LLMInvoker.safe_invoke_video_analysis(
            "判断视频倾向",
            video_file,
            prompt,
            agent=self,
            fallback={"判断结果": "概念更重要"}
        )

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

        return result

    def _step2_extract_primary(
        self,
        tendency_result: dict,
        video_file,
        topic_description: dict
    ) -> dict:
        """Step 2: 主视角提取

        从更重要的视角提取（概念 or 元素）

        职责：只负责提取实质点，不做深度分析
        - 全面识别候选实质点
        - 后续由 Step 4 负责统计和深度理解

        参数说明：
        - video_file: Gemini视频文件对象
        - topic_description: 选题描述，帮助理解提取范围和主题方向
        """
        logger.info("=" * 80)
        logger.info("【Step 2】主视角提取 - 从更重要的视角提取")
        logger.info("=" * 80)

        # 获取判断结果
        tendency = tendency_result.get("判断结果", "概念更重要")
        primary_type = "概念" if tendency == "概念更重要" else "元素"

        logger.info(f"主视角类型: {primary_type}")

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

        # 构建prompt
        prompt = f"""# 选题描述（仅供参考理解视频方向）

{topic_text}

**重要约束**：选题描述仅用于帮助你理解视频的主题方向和定位，**不要从选题描述中提取实质点**。所有实质点必须从**视频实际内容**中提取。

# 视频倾向判断

{tendency_result.get("判断理由", "")}

# 任务

从**{primary_type}视角**提取视频中的核心**{primary_type}**。

## {primary_type}定义

"""

        if primary_type == "概念":
            prompt += """### 概念（Concept）
**定义**：抽象的名词，不能是形式的

**特征**：
- ✅ 必须是名词
- ✅ 抽象的，非具象的
- ✅ 不能是形式的（不是风格、颜色、材质等形式特征）
- ✅ 虽然没有直接提到，但隐含在内容中
- ✅ 对理解视频核心价值至关重要

**原子名词约束**：
- ❌ 禁止形容词
- ❌ 禁止动词
- ✅ 只能是纯粹的名词形式
"""
        else:
            prompt += """### 元素（Element）
**定义**：具体的、客观存在的实体，视觉可见

**特征**：
- ✅ 必须是名词
- ✅ 具体的物体，视觉可见
- ✅ 客观存在的实体
- ✅ 使用原子名词（最基础的名词形式，去除修饰语）
- ❌ 不是动作、状态、情绪
- ❌ 不是抽象概念
"""

        prompt += f"""
## 提取原则

1. **全面识别**：宁可多识别，后续会筛选
2. **名词化**：所有{primary_type}必须是名词形式
3. **原子化**：使用最基础的名词,去除修饰语
4. **聚焦主视角**：只提取{primary_type}，不提取另一种类型
5. **仅从视频内容提取**：只从视频中提取，**禁止从选题描述中提取**

## 输出要求

每个{primary_type}需要标注：
- 名称：原子名词
- 识别依据：为什么识别这个{primary_type}（简要说明）

# 输出（JSON）

{{{{
  "实质点列表": [
    {{{{
      "名称": "{primary_type}名称（原子名词）",
      "类型": "{primary_type}",
      "识别依据": "为什么识别这个{primary_type}"
    }}}}
  ]
}}}}"""

        # 调用LLM（使用视频分析接口）
        result = LLMInvoker.safe_invoke_video_analysis(
            f"主视角提取（{primary_type}）",
            video_file,
            prompt,
            agent=self,
            fallback={"实质点列表": []}
        )

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

        return result

    def _step3_extract_secondary(
        self,
        tendency_result: dict,
        primary_result: dict,
        video_file,
        topic_description: dict
    ) -> dict:
        """Step 3: 辅视角提取

        从另一个视角提取，并与主视角去重

        职责：只负责提取实质点，不做深度分析
        - 从辅视角识别候选实质点
        - 与主视角去重，避免重复提取
        - 后续由 Step 4 负责统计和深度理解

        参数说明：
        - video_file: Gemini视频文件对象
        - topic_description: 选题描述，帮助理解提取范围和主题方向
        """
        logger.info("=" * 80)
        logger.info("【Step 3】辅视角提取 - 从另一个视角提取，并与主视角去重")
        logger.info("=" * 80)

        # 获取判断结果
        tendency = tendency_result.get("判断结果", "概念更重要")
        secondary_type = "元素" if tendency == "概念更重要" else "概念"

        logger.info(f"辅视角类型: {secondary_type}")

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

        # 构建主视角实质点文本
        primary_items = primary_result.get("实质点列表", [])
        primary_text = ""
        for item in primary_items:
            primary_text += f"\n- {item.get('名称', 'N/A')}"

        # 构建prompt
        prompt = f"""# 选题描述（仅供参考理解视频方向）

{topic_text}

**重要约束**：选题描述仅用于帮助你理解视频的主题方向和定位，**不要从选题描述中提取实质点**。所有实质点必须从**视频实际内容**中提取。

# 主视角已提取的实质点

{primary_text}

# 任务

从**{secondary_type}视角**提取视频中的核心**{secondary_type}**，并与主视角提取的实质点去重。

## {secondary_type}定义

"""

        if secondary_type == "概念":
            prompt += """### 概念（Concept）
**定义**：抽象的名词，不能是形式的

**特征**：
- ✅ 必须是名词
- ✅ 抽象的，非具象的
- ✅ 不能是形式的（不是风格、颜色、材质等形式特征）
- ✅ 虽然没有直接提到，但隐含在内容中
- ✅ 对理解视频核心价值至关重要

**原子名词约束**：
- ❌ 禁止形容词
- ❌ 禁止动词
- ✅ 只能是纯粹的名词形式
"""
        else:
            prompt += """### 元素（Element）
**定义**：具体的、客观存在的实体，视觉可见

**特征**：
- ✅ 必须是名词
- ✅ 具体的物体，视觉可见
- ✅ 客观存在的实体
- ✅ 使用原子名词（最基础的名词形式，去除修饰语）
- ❌ 不是动作、状态、情绪
- ❌ 不是抽象概念
"""

        prompt += f"""
## 提取原则

1. **全面识别**：宁可多识别，后续会筛选
2. **名词化**：所有{secondary_type}必须是名词形式
3. **原子化**：使用最基础的名词，去除修饰语
4. **聚焦辅视角**：只提取{secondary_type}，不提取另一种类型
5. **仅从视频内容提取**：只从视频中提取，**禁止从选题描述中提取**

## 去重要求

**关键**：与主视角提取的实质点去重

- 如果{secondary_type}与主视角实质点存在语义关联，不要重复提取
- 只提取主视角中没有覆盖到的{secondary_type}

## 输出要求

每个{secondary_type}需要标注：
- 名称：原子名词
- 识别依据：为什么识别这个{secondary_type}（简要说明）

# 输出（JSON）

{{{{
  "实质点列表": [
    {{{{
      "名称": "{secondary_type}名称（原子名词）",
      "类型": "{secondary_type}",
      "识别依据": "为什么识别这个{secondary_type}"
    }}}}
  ]
}}}}"""

        # 调用LLM（使用视频分析接口）
        result = LLMInvoker.safe_invoke_video_analysis(
            f"辅视角提取（{secondary_type}）",
            video_file,
            prompt,
            agent=self,
            fallback={"实质点列表": []}
        )

        # 打印结果
        logger.info(f"Step 3 结果:\n{json.dumps(result, ensure_ascii=False, indent=2)}")

        return result

    def _step4_quantitative_analysis(
        self,
        all_substantials: list,
        video_file,
        section_division: dict
    ) -> dict:
        """Step 4: 共性定量分析 + 段落支撑评分（合并）

        对所有实质点进行：
        1. 共性定量分析：出现段落数、段落覆盖率、出现频次
        2. 段落支撑评分：评估每个实质点在各段落中的支撑得分（0-10分）
        """
        logger.info("=" * 80)
        logger.info("【Step 4】共性定量分析 + 段落支撑评分")
        logger.info("=" * 80)

        if not all_substantials:
            logger.warning("⚠️  没有实质点可供分析")
            return {"分析结果": []}

        logger.info(f"输入: {len(all_substantials)} 个实质点")

        # 构建内容
        section_text = self._build_section_text(section_division)

        # 提取段落总数
        total_sections = self._count_sections(section_division)

        # 构建实质点列表文本
        substantials_text = ""
        for s in all_substantials:
            substantials_text += f"\n- {s.get('名称', 'N/A')} ({s.get('类型', 'N/A')})"
            substantials_text += f"\n  识别依据: {s.get('识别依据', 'N/A')}"

        # 构建prompt
        prompt = f"""# 段落划分结果

{section_text}

段落总数: {total_sections}

# 实质点列表

{substantials_text}

# 任务

对每个实质点进行**共性定量分析 + 段落支撑评分**。

**重要**：只输出新增的分析数据和评分数据，不要重复输出名称、类型、识别依据等已有信息。

## 第一部分：共性分析（元素和概念统一指标）

1. **出现段落数**：该实质点在多少个段落中出现（整数）
2. **段落覆盖率**：出现段落数 / 总段落数（百分比，保留2位小数）
3. **出现频次**：该实质点总共出现多少次（整数）
4. **出现段落列表**：该实质点出现的段落ID列表

**关键约束 - 段落统计规则**：
- **只统计具体段落**（叶子节点），不统计抽象段落（父节点）
- 例如：段落1是段落1.1的父节点，如果元素出现在段落1.1中，只记录段落1.1，不记录段落1
- 判断方法：有子项的段落是抽象段落，没有子项的段落是具体段落
- 段落总数={total_sections}已经是具体段落的总数

**说明**：
- 元素：统计该元素在视频中出现的段落和频次
- 概念：统计该概念在视频中被提及或隐含的段落和频次

## 第二部分：段落支撑评分（每个段落0-10分）

评估该实质点在每个出现的段落中的权重/重要性

**关键约束 - 段落评分规则**：
- **只评估具体段落**（叶子节点），不评估抽象段落（父节点）
- 与第一部分的段落统计保持一致
- 段落ID必须与"出现段落列表"中的ID一致

**评分标准**：
- 9-10分：段落核心元素，不可或缺
- 7-8分：段落重要元素，有明显作用
- 5-6分：段落一般元素，有一定作用
- 3-4分：段落辅助元素，作用较小
- 0-2分：段落边缘元素或未出现

**依据要求**：
- 可以参考共性分析中的段落出现数据
- 必须逐个段落评分（只对出现的段落评分）
- 必须说明在该段落中的具体表现
- 必须提供客观的、直接的证据

## 输出要求

- 定量指标必须准确计算
- 所有评分必须有客观依据
- **只输出名称和对应的分析+评分数据，不要重复已有字段**

# 输出（JSON）

{{
  "分析结果": [
    {{
      "名称": "实质点名称",
      "出现段落数": 3,
      "段落覆盖率": 0.75,
      "出现频次": 5,
      "出现段落列表": ["段落1", "段落1.1", "段落2.1"],
      "支撑段落详情": [
        {{
          "段落ID": "段落1",
          "得分": 8,
          "依据": "在该段落中的具体表现 + 客观证据"
        }},
        {{
          "段落ID": "段落1.1",
          "得分": 6,
          "依据": "在该段落中的具体表现 + 客观证据"
        }}
      ]
    }}
  ]
}}

**注意**：
- 元素和概念使用统一的分析指标
- **出现段落列表**: 必须使用具体段落ID列表（只包含叶子节点，不包含父节点）。例如：如果元素出现在段落1.1中，只记录["段落1.1"]，不记录["段落1", "段落1.1"]
- **支撑段落详情**: 必须使用段落ID字段，包含得分和依据，只对具体段落评分（与"出现段落列表"一致）
- 不要输出类型、识别依据等字段，这些会由代码自动合并"""

        # 调用LLM（使用视频分析接口）
        llm_result = LLMInvoker.safe_invoke_video_analysis(
            "共性定量分析+段落支撑评分",
            video_file,
            prompt,
            agent=self,
            fallback={"分析结果": []}
        )

        # 代码合并：将原始实质点数据与分析数据合并
        analyzed_items = []
        analysis_results = llm_result.get("分析结果", [])

        for analysis in analysis_results:
            analysis_name = analysis.get("名称", "")
            # 找到对应的原始实质点
            original = None
            for s in all_substantials:
                if s.get("名称") == analysis_name:
                    original = s
                    break

            if original:
                # 合并数据：原始数据 + 分析数据
                merged_item = {
                    **original,  # 保留原始的名称、类型、识别依据
                    **{k: v for k, v in analysis.items() if k != "名称"}  # 添加分析数据（排除名称）
                }
                analyzed_items.append(merged_item)
            else:
                logger.warning(f"⚠️  未找到实质点 '{analysis_name}' 的原始数据")
                # 如果找不到原始数据，仍然保留分析结果
                analyzed_items.append(analysis)

        result = {"分析结果": analyzed_items}

        # 打印结果
        logger.info(f"Step 4 合并后结果:\n{json.dumps(result, ensure_ascii=False, indent=2)}")

        return result

    def _step5_multi_dimensional_scoring(
        self,
        analyzed_result: dict,
        video_file,
        inspiration_points: dict,
        purpose_points: list,
        key_points: dict
    ) -> dict:
        """Step 5: 多维度支撑评分（3个维度并行评估）

        对所有实质点进行3个维度的支撑性评分（并行）：
        1. 支撑灵感点
        2. 支撑目的点
        3. 支撑关键点

        注意：段落支撑评分已经在Step4中完成

        参数说明：
        - analyzed_result: 包含共性分析数据和段落支撑评分
        - video_file: Gemini视频文件对象
        - inspiration_points/purpose_points/key_points: 用于评估支撑性
        """
        logger.info("=" * 80)
        logger.info("【Step 5】多维度支撑评分 - 3个维度并行评估")
        logger.info("=" * 80)

        analyzed_items = analyzed_result.get("分析结果", [])
        if not analyzed_items:
            logger.warning("⚠️  没有分析结果可供评分")
            return {"评分结果": []}

        logger.info(f"输入: {len(analyzed_items)} 个分析实质点")

        # 并行评估3个维度（通过3次独立的LLM调用）
        logger.info("\n开始并行评估3个维度...")

        # 评估维度1: 支撑灵感点
        logger.info("  [1/3] 评估支撑灵感点...")
        inspiration_scoring = self._evaluate_inspiration_support(
            analyzed_items, video_file, inspiration_points
        )

        # 评估维度2: 支撑目的点
        logger.info("  [2/3] 评估支撑目的点...")
        purpose_scoring = self._evaluate_purpose_support(
            analyzed_items, video_file, purpose_points
        )

        # 评估维度3: 支撑关键点
        logger.info("  [3/3] 评估支撑关键点...")
        key_scoring = self._evaluate_key_support(
            analyzed_items, video_file, key_points
        )

        # 合并3个维度的评分结果
        logger.info("\n合并3个维度的评分结果...")
        scored_items = self._merge_scoring_results(
            analyzed_items,
            inspiration_scoring,
            purpose_scoring,
            key_scoring
        )

        result = {"评分结果": scored_items}

        # 打印结果
        logger.info(f"Step 5 合并后结果:\n{json.dumps(result, ensure_ascii=False, indent=2)}")

        return result

    def _evaluate_inspiration_support(
        self,
        analyzed_items: list,
        video_file,
        inspiration_points: dict
    ) -> dict:
        """评估支撑灵感点"""
        # 构建内容
        items_text = self._build_analyzed_items_text(analyzed_items)
        inspiration_text = self._build_points_text("灵感点", inspiration_points)

        # 构建prompt
        prompt = f"""# 实质点列表（含共性分析数据和段落支撑评分）

{items_text}

# 灵感点

{inspiration_text}

# 任务

对每个实质点进行**支撑灵感点评分**（0-10分）。

**重要**：只输出实质点名称和灵感点评分，不要输出其他已有信息。

## 评分维度：支撑灵感点（0-10分）

评估该实质点如何支撑灵感点（触发创作者的灵感）

**评分标准**：
- 9-10分：核心支撑，直接触发创作灵感
- 7-8分：重要支撑，对灵感有明显作用
- 5-6分：一定支撑，有一定触发作用
- 3-4分：辅助支撑，间接相关
- 0-2分：弱支撑或无关

**依据要求**：
- 必须引用具体的灵感点
- 必须说明实质点如何支撑该灵感点
- 必须提供客观的、直接的证据（引用视频内容）
- 可以利用共性分析数据和段落支撑评分辅助判断

## 输出要求

- 所有评分必须有客观依据
- 所有依据必须引用视频内容
- **只输出名称和灵感点评分数据**

# 输出（JSON）

{{
  "评分结果": [
    {{
      "名称": "实质点名称",
      "支撑灵感点得分": 8,
      "支撑灵感点依据": "引用具体灵感点 + 如何支撑 + 客观证据"
    }}
  ]
}}

**注意**：
- 只输出名称和支撑灵感点的评分数据
- 不要输出类型、识别依据、共性分析、段落支撑等字段"""

        # 调用LLM（使用视频分析接口）
        result = LLMInvoker.safe_invoke_video_analysis(
            "评估支撑灵感点",
            video_file,
            prompt,
            agent=self,
            fallback={"评分结果": []}
        )

        return result

    def _evaluate_purpose_support(
        self,
        analyzed_items: list,
        video_file,
        purpose_points: list
    ) -> dict:
        """评估支撑目的点"""
        # 构建内容
        items_text = self._build_analyzed_items_text(analyzed_items)
        purpose_text = self._build_points_text("目的点", purpose_points)

        # 构建prompt
        prompt = f"""# 实质点列表（含共性分析数据和段落支撑评分）

{items_text}

# 目的点

{purpose_text}

# 任务

对每个实质点进行**支撑目的点评分**（0-10分）。

**重要**：只输出实质点名称和目的点评分，不要输出其他已有信息。

## 评分维度：支撑目的点（0-10分）

评估该实质点如何支撑目的点（帮助达成创作目的）

**评分标准**：
- 9-10分：核心支撑，直接承载创作目的
- 7-8分：重要支撑，对目的达成有明显作用
- 5-6分：一定支撑，有一定帮助
- 3-4分：辅助支撑，间接相关
- 0-2分：弱支撑或无关

**依据要求**：
- 必须引用具体的目的点
- 必须说明实质点如何支撑该目的点
- 必须提供客观的、直接的证据（引用视频内容）
- 可以利用共性分析数据和段落支撑评分辅助判断

## 输出要求

- 所有评分必须有客观依据
- 所有依据必须引用视频内容
- **只输出名称和目的点评分数据**

# 输出（JSON）

{{
  "评分结果": [
    {{
      "名称": "实质点名称",
      "支撑目的点得分": 7,
      "支撑目的点依据": "引用具体目的点 + 如何支撑 + 客观证据"
    }}
  ]
}}

**注意**：
- 只输出名称和支撑目的点的评分数据
- 不要输出类型、识别依据、共性分析、段落支撑等字段"""

        # 调用LLM（使用视频分析接口）
        result = LLMInvoker.safe_invoke_video_analysis(
            "评估支撑目的点",
            video_file,
            prompt,
            agent=self,
            fallback={"评分结果": []}
        )

        return result

    def _evaluate_key_support(
        self,
        analyzed_items: list,
        video_file,
        key_points: dict
    ) -> dict:
        """评估支撑关键点"""
        # 构建内容
        items_text = self._build_analyzed_items_text(analyzed_items)
        key_text = self._build_points_text("关键点", key_points)

        # 构建prompt
        prompt = f"""# 实质点列表（含共性分析数据和段落支撑评分）

{items_text}

# 关键点

{key_text}

# 任务

对每个实质点进行**支撑关键点评分**（0-10分）。

**重要**：只输出实质点名称和关键点评分，不要输出其他已有信息。

## 评分维度：支撑关键点（0-10分）

评估该实质点如何支撑关键点（吸引消费者）

**评分标准**：
- 9-10分：核心支撑，直接吸引消费者
- 7-8分：重要支撑，有明显吸引力
- 5-6分：一定支撑，有一定吸引作用
- 3-4分：辅助支撑，间接相关
- 0-2分：弱支撑或无关

**依据要求**：
- 必须引用具体的关键点
- 必须说明实质点如何支撑该关键点
- 必须提供客观的、直接的证据（引用视频内容）
- 可以利用共性分析数据和段落支撑评分辅助判断

## 输出要求

- 所有评分必须有客观依据
- 所有依据必须引用视频内容
- **只输出名称和关键点评分数据**

# 输出（JSON）

{{
  "评分结果": [
    {{
      "名称": "实质点名称",
      "支撑关键点得分": 9,
      "支撑关键点依据": "引用具体关键点 + 如何支撑 + 客观证据"
    }}
  ]
}}

**注意**：
- 只输出名称和支撑关键点的评分数据
- 不要输出类型、识别依据、共性分析、段落支撑等字段"""

        # 调用LLM（使用视频分析接口）
        result = LLMInvoker.safe_invoke_video_analysis(
            "评估支撑关键点",
            video_file,
            prompt,
            agent=self,
            fallback={"评分结果": []}
        )

        return result

    def _merge_scoring_results(
        self,
        analyzed_items: list,
        inspiration_scoring: dict,
        purpose_scoring: dict,
        key_scoring: dict
    ) -> list:
        """合并3个维度的评分结果"""
        # 创建名称到评分的映射
        inspiration_map = {item.get("名称"): item for item in inspiration_scoring.get("评分结果", [])}
        purpose_map = {item.get("名称"): item for item in purpose_scoring.get("评分结果", [])}
        key_map = {item.get("名称"): item for item in key_scoring.get("评分结果", [])}

        # 合并所有数据
        scored_items = []
        for analyzed_item in analyzed_items:
            name = analyzed_item.get("名称")

            # 合并数据
            merged_item = {
                **analyzed_item,  # 保留分析数据（包含段落支撑评分）
            }

            # 添加灵感点评分
            if name in inspiration_map:
                inspiration_data = inspiration_map[name]
                merged_item["支撑灵感点得分"] = inspiration_data.get("支撑灵感点得分", 0)
                merged_item["支撑灵感点依据"] = inspiration_data.get("支撑灵感点依据", "")

            # 添加目的点评分
            if name in purpose_map:
                purpose_data = purpose_map[name]
                merged_item["支撑目的点得分"] = purpose_data.get("支撑目的点得分", 0)
                merged_item["支撑目的点依据"] = purpose_data.get("支撑目的点依据", "")

            # 添加关键点评分
            if name in key_map:
                key_data = key_map[name]
                merged_item["支撑关键点得分"] = key_data.get("支撑关键点得分", 0)
                merged_item["支撑关键点依据"] = key_data.get("支撑关键点依据", "")

            scored_items.append(merged_item)

        return scored_items

    def _build_analyzed_items_text(self, analyzed_items: list) -> str:
        """构建分析实质点列表文本（包含共性分析数据和段落支撑评分）"""
        items_text = ""
        for item in analyzed_items:
            items_text += f"\n- {item.get('名称', 'N/A')} ({item.get('类型', 'N/A')})"
            items_text += f"\n  出现段落数: {item.get('出现段落数', 0)}"
            items_text += f"\n  段落覆盖率: {item.get('段落覆盖率', 0)}"
            items_text += f"\n  出现频次: {item.get('出现频次', 0)}"

            # 添加段落支撑评分摘要
            section_details = item.get('支撑段落详情', [])
            if section_details:
                avg_section_score = sum(s.get("得分", 0) for s in section_details) / len(section_details)
                items_text += f"\n  段落支撑平均得分: {avg_section_score:.1f}"

        return items_text
    def _step6_filter(self, scored_result: dict) -> dict:
        """Step 6: 筛选

        筛选标准：
        1. 支撑性综合得分 > 6.0
        2. 或者任一单项得分 > 8.0
        3. 或者共性得分 > 7.0（基于多段落共性数据）
        """
        logger.info("=" * 80)
        logger.info("【Step 6】筛选核心实质点")
        logger.info("=" * 80)

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

        logger.info(f"输入: {len(scored_items)} 个评分实质点")
        logger.info("\n筛选规则:")
        logger.info("  1. 综合得分 > 6.0")
        logger.info("  2. 或任一单项得分 > 8.0")
        logger.info("  3. 或共性得分 > 7.0")

        filtered_items = []

        for item in scored_items:
            name = item.get("名称", "未知")

            # 提取支撑性得分
            inspiration_score = item.get("支撑灵感点得分", 0)
            purpose_score = item.get("支撑目的点得分", 0)
            key_score = item.get("支撑关键点得分", 0)

            # 计算平均段落得分
            section_details = item.get("支撑段落详情", [])
            avg_section_score = 0
            if section_details:
                avg_section_score = sum(s.get("得分", 0) for s in section_details) / len(section_details)

            # 计算综合得分
            comprehensive_score = (inspiration_score + purpose_score + key_score + avg_section_score) / 4

            # 判断是否通过筛选
            max_single_score = max(inspiration_score, purpose_score, key_score, avg_section_score)

            # 计算共性得分（基于 Step 4 的共性数据）
            # 统一计算方式：覆盖率(70%) + 频次归一化(30%)
            coverage_rate = item.get("段落覆盖率", 0)  # 0-1
            frequency = item.get("出现频次", 0)  # 整数

            # 频次归一化：假设超过10次为满分
            normalized_frequency = min(frequency / 10, 1.0)

            # 共性得分 = 覆盖率权重(70%) + 频次权重(30%)
            # 转换为 0-10 分制
            commonality_score = (coverage_rate * 0.7 + normalized_frequency * 0.3) * 10

            # 三个条件满足任意一个即通过
            if comprehensive_score > 6.0 or max_single_score > 8.0 or commonality_score > 7.0:
                logger.info(
                    f"  ✓ {name}: 通过筛选 "
                    f"(综合={comprehensive_score:.2f}, "
                    f"最高单项={max_single_score:.2f}, "
                    f"共性={commonality_score:.2f})"
                )
                filtered_items.append(item)
            else:
                logger.info(
                    f"  ✗ {name}: 未通过筛选 "
                    f"(综合={comprehensive_score:.2f}, "
                    f"最高单项={max_single_score:.2f}, "
                    f"共性={commonality_score:.2f})"
                )

        # 打印结果
        logger.info(f"Step 6 结果:\n{json.dumps({'筛选结果': filtered_items}, ensure_ascii=False, indent=2)}")

        return {"筛选结果": filtered_items}

    def _step7_deduplicate(
        self,
        filtered_result: dict
    ) -> dict:
        """Step 7: 去重（同类去重）

        只进行同类去重：概念与概念去重，元素与元素去重

        参数说明：
        - filtered_result: 筛选后的实质点，包含所有历史数据（名称、类型、识别依据、共性分析、评分等）
        """
        if not self.is_initialized:
            self.initialize()

        logger.info("=" * 80)
        logger.info("【Step 7】去重 - 同类去重（概念与概念，元素与元素）")
        logger.info("=" * 80)

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

        logger.info(f"输入: {len(filtered_items)} 个筛选后的实质点")

        # 构建筛选实质点文本（包含关键信息辅助去重判断）
        items_text = ""
        for item in filtered_items:
            items_text += f"\n- {item.get('名称', 'N/A')} ({item.get('类型', 'N/A')})"
            items_text += f"\n  识别依据: {item.get('识别依据', 'N/A')}"

            # 计算综合得分
            inspiration_score = item.get("支撑灵感点得分", 0)
            purpose_score = item.get("支撑目的点得分", 0)
            key_score = item.get("支撑关键点得分", 0)
            section_details = item.get("支撑段落详情", [])
            avg_section_score = 0
            if section_details:
                avg_section_score = sum(s.get("得分", 0) for s in section_details) / len(section_details)
            comprehensive_score = (inspiration_score + purpose_score + key_score + avg_section_score) / 4

            items_text += f"\n  综合得分: {comprehensive_score:.2f}"
            items_text += f"\n  出现段落数: {item.get('出现段落数', 0)}"

        # 构建prompt
        prompt = f"""# 筛选后的实质点列表

{items_text}

# 任务

对筛选后的实质点进行**同类去重**。

**重要**：只输出去重决策和保留的实质点名称列表，不要输出完整的实质点数据。

## 去重规则

**核心原则**：只进行同类去重，概念与概念去重，元素与元素去重，不做跨类型去重。

### 规则1: 概念 vs 概念
- 如果两个概念语义相似或有包含关系，保留**综合得分更高**的那个

### 规则2: 元素 vs 元素
- 如果两个元素是同一类物体的不同表述，保留**综合得分更高**的那个

**重要约束**：
- ✅ 概念只与概念比较去重
- ✅ 元素只与元素比较去重
- ❌ 概念与元素之间**不进行**去重比较

## 去重流程

1. **分组**：将实质点按类型分为概念组和元素组
2. **概念组内去重**：找出概念组内语义相似的概念，保留得分更高的
3. **元素组内去重**：找出元素组内同类物体，保留得分更高的
4. **合并结果**：将去重后的概念和元素合并

# 输出（JSON）

{{
  "去重决策": [
    {{
      "冲突组": ["实质点1", "实质点2"],
      "冲突类型": "概念vs概念/元素vs元素",
      "应用规则": "规则1/规则2",
      "保留": "实质点1",
      "去除": ["实质点2"],
      "决策理由": "为什么保留实质点1，去除实质点2"
    }}
  ],
  "保留列表": ["实质点名称1", "实质点名称2", ...]
}}

**注意**：
- 只输出去重决策和保留的实质点名称
- 不要输出完整的实质点数据（类型、识别依据、评分等）
- 这些完整数据会由代码自动填充"""

        # 构建消息
        messages = [
            {"role": "system", "content": self.system_prompt},
            {"role": "user", "content": prompt}
        ]

        # 调用LLM
        llm_result = LLMInvoker.safe_invoke(
            self,
            "去重",
            messages,
            fallback={"保留列表": []}
        )

        # 代码执行去重：基于保留列表提取完整数据
        deduplicated_items = []
        retained_names = llm_result.get("保留列表", [])

        for name in retained_names:
            # 找到对应的完整数据
            full_item = None
            for item in filtered_items:
                if item.get("名称") == name:
                    full_item = item
                    break

            if full_item:
                deduplicated_items.append(full_item)
            else:
                logger.warning(f"⚠️  未找到实质点 '{name}' 的完整数据")

        result = {
            "去重决策": llm_result.get("去重决策", []),
            "去重结果": deduplicated_items
        }

        # 打印结果
        logger.info(f"Step 7 合并后结果:\n{json.dumps(result, ensure_ascii=False, indent=2)}")

        return result

    def _step8_integrate(
        self,
        deduplicated_result: dict
    ) -> dict:
        """Step 8: 整合

        为实质点添加分类标签（用方括号表示）

        参数说明：
        - deduplicated_result: 去重后的实质点列表，包含所有历史数据（名称、类型、识别依据、共性分析、评分等）
        """
        if not self.is_initialized:
            self.initialize()

        logger.info("=" * 80)
        logger.info("【Step 8】整合 - 为实质点添加分类标签")
        logger.info("=" * 80)

        deduplicated_items = deduplicated_result.get("去重结果", [])
        if not deduplicated_items:
            logger.warning("⚠️  没有去重结果可供整合")
            return {"元素列表": []}

        logger.info(f"输入: {len(deduplicated_items)} 个去重后的实质点")

        # 构建去重实质点文本（包含关键信息辅助分类）
        items_text = ""
        for item in deduplicated_items:
            items_text += f"\n- {item.get('名称', 'N/A')} ({item.get('类型', 'N/A')})"
            items_text += f"\n  识别依据: {item.get('识别依据', 'N/A')}"

        # 构建prompt
        prompt = f"""# 去重后的实质点列表

{items_text}

# 任务

为每个实质点分配分类，分类用字典表示层级关系。

**重要**：只输出实质点名称和对应的分类字典，不要输出完整的实质点数据。

## 分类规则

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

- **What 维度**：回答"这是什么类型的东西"
- **分类来源**：从实质点本身的本质属性中抽象出分类
- **同级正交**：同一分类下的节点应该具有相同的 What 维度

**MECE分类视角**：
- 物理维度：基于物理属性
- 化学维度：基于化学成分
- 生物维度：基于生物分类
- 功能维度：基于客观功能

**禁止**：
- ❌ 从"对其他事物的影响/效果"角度分类
- ❌ 从"目的/价值"角度分类

## 分类层级

- 根据实际情况灵活确定层级深度

## 输出要求

- 同级正交：同一分类下的实质点应该具有相同的 What 维度
- **只输出实质点名称和对应的分类字典**

# 输出（JSON）

{{
  "分类结果": [
    {{
      "名称": "实质点名称",
      "分类": {{"一级": "分类名", "二级": "子分类名"}}
    }}
  ]
}}

**注意**：
- 只输出名称和分类字典
- 分类字典的key必须是"一级"、"二级"、"三级"等
- 不要输出类型、识别依据、评分等字段
- 这些完整数据会由代码自动填充"""

        # 构建消息
        messages = [
            {"role": "system", "content": self.system_prompt},
            {"role": "user", "content": prompt}
        ]

        # 调用LLM
        llm_result = LLMInvoker.safe_invoke(
            self,
            "整合（添加分类）",
            messages,
            fallback={"分类结果": []}
        )

        # 代码合并：将分类添加到实质点中
        integrated_items = []
        classification_results = llm_result.get("分类结果", [])

        for classification in classification_results:
            name = classification.get("名称", "")
            category = classification.get("分类", "")

            # 找到对应的完整数据
            full_item = None
            for item in deduplicated_items:
                if item.get("名称") == name:
                    full_item = item
                    break

            if full_item:
                # 创建新的实质点对象，添加分类字段
                integrated_item = {
                    **full_item,  # 保留所有历史数据
                    "分类": category  # 添加分类字段
                }
                integrated_items.append(integrated_item)
            else:
                logger.warning(f"⚠️  未找到实质点 '{name}' 的完整数据")

        result = {"元素列表": integrated_items}

        # 打印结果
        logger.info(f"Step 8 整合后结果:\n{json.dumps(result, ensure_ascii=False, indent=2)}")

        return result

    # ========== 辅助方法 ==========

    def _build_video_content(self, video_url: str) -> str:
        """构建视频内容文本"""
        if video_url:
            return f"视频URL: {video_url}"
        return "无视频信息"

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

    def _build_section_text(self, section_division: dict) -> str:
        """构建段落划分文本（递归展示所有层级，包含ID）"""
        if not section_division:
            return "无段落划分信息"

        sections = section_division.get("段落列表", [])
        if not sections:
            return "无段落信息"

        def build_section_list(section_list, indent=0):
            """递归构建段落列表文本"""
            text = ""
            for section in section_list:
                prefix = "  " * indent
                section_id = section.get('id', 'N/A')
                section_desc = section.get('描述', 'N/A')
                content_range = section.get('内容范围', 'N/A')

                text += f"{prefix}- {section_id}: {section_desc}\n"
                text += f"{prefix}  内容范围: {content_range}\n"

                # 递归处理子项
                if section.get('子项'):
                    text += build_section_list(section['子项'], indent + 1)

            return text

        text = "段落列表:\n"
        text += build_section_list(sections)

        return text

    def _build_points_text(self, point_type: str, points_data) -> str:
        """构建点（灵感点/目的点/关键点）的文本"""
        if not points_data:
            return f"无{point_type}信息"

        if isinstance(points_data, dict):
            # 处理字典格式（如灵感点）
            text = f"{point_type}:\n"
            for key, value in points_data.items():
                if isinstance(value, list):
                    text += f"- {key}:\n"
                    for item in value:
                        if isinstance(item, dict):
                            text += f"  - {item.get('点', item.get('关键点', 'N/A'))}\n"
                        else:
                            text += f"  - {item}\n"
                else:
                    text += f"- {key}: {value}\n"
            return text
        elif isinstance(points_data, list):
            # 处理列表格式（如目的点）
            text = f"{point_type}:\n"
            for item in points_data:
                if isinstance(item, dict):
                    text += f"- {item.get('目的点', item.get('点', 'N/A'))}\n"
                else:
                    text += f"- {item}\n"
            return text
        else:
            return f"{point_type}: {points_data}"

    def _count_sections(self, section_division: dict) -> int:
        """统计段落总数（只统计叶子节点，即具体段落）"""
        if not section_division:
            return 0

        sections = section_division.get("段落列表", [])
        if not sections:
            return 0

        def count_leaf_nodes(section_list):
            """只统计叶子节点（没有子项的段落）"""
            count = 0
            for section in section_list:
                children = section.get("子项", [])
                if children:
                    # 有子项,是父节点,递归统计子项
                    count += count_leaf_nodes(children)
                else:
                    # 无子项,是叶子节点,计数+1
                    count += 1
            return count

        return count_leaf_nodes(sections)

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

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