#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
灵感点提取Agent
"""

from typing import Any, Dict, List, TYPE_CHECKING
import json

if TYPE_CHECKING:
    from google import genai

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__)

LOG_SEPARATOR = "=" * 80

# 配置常量
HISTORICAL_POSTS_BATCH_SIZE = 5  # 每批处理的历史帖子数量
MIN_QUALITY_SCORE = 15  # 最低质量分数阈值（总分3-30）

# ==================== 数据结构定义 ====================


class InspirationPointsAgent(BaseLLMAgent):
    """灵感点提取Agent - 从创作者视角提取触发创作的刺激源
    """

    def __init__(
        self,
        name: str = "inspiration_points_agent",
        description: str = "灵感点提取Agent - 从创作者视角提取小众、低概率、原发的创作灵感",
        model_provider: str = "google_genai",
        temperature: float = 0.7,
        max_tokens: int = 20480
    ):
        """
        初始化灵感点提取Agent

        Args:
            name: Agent名称
            description: Agent描述
            model_provider: 模型提供商
            temperature: 生成温度，控制创造性
            max_tokens: 最大token数
        """
        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:
        """构建系统提示词 - 只包含所有steps共享的核心信息"""
        return """你是创作灵感分析专家，从创作者视角逆向推导触发创作的深层刺激源。

# 灵感点定义（核心概念）
**灵感点** = 创作前的触发源（让创作者产生创作冲动的刺激）
- 区分：灵感点（触发源）≠ 目的点（动机）≠ 关键点（吸引点）
- 验证：能否从这个灵感点推导出整个视频内容

# 核心原则（所有分析遵循）
- 透过表面内容挖掘深层心理动机、行为模式、价值观冲突
- 灵感点可以来自视频的任何部分，包括画面、动作、场景、对话等
- 视频的画面和音频同样重要,要捕捉动态变化和细节"""

    def process(self, state: Dict[str, Any], config=None) -> Dict[str, Any]:
        """处理状态 - 多步骤提取灵感点（视频分析版本）

        流程：
        1. 识别候选灵感点（逐帧分析视频）
        2. 逆向推导验证
        3. 去重检查
        4. 三维度评分
        5. 排序筛选
        6. 组装最终输出
        """
        if not self.is_initialized:
            self.initialize()

        logger.info("灵感点分析开始")

        try:
            # 从 state 中获取视频URL和文本信息
            video_uri = state.get("video_uploaded_uri", "")
            video_upload_error = state.get("video_upload_error")
            video_url = state.get("video", "")
            # 优先使用原始URL，如果没有则使用上传后的URI
            video_url_for_prompt = video_url if video_url else video_uri
            text_data = {
                "title": state.get("title", ""),
                "body": state.get("body_text", "")
            }

            if not video_uri:
                # 检查是否有上传错误信息
                error_msg = "未找到视频URI，无法进行分析"
                if video_upload_error:
                    error_msg += f"。视频上传失败: {video_upload_error}"
                elif video_url:
                    error_msg += f"。原始视频URL: {video_url}"
                else:
                    error_msg += "。未提供视频URL"
                
                logger.error(error_msg)
                return {
                    "inspiration_points": [],
                    "metadata": {
                        "error": error_msg,
                        "video_upload_error": video_upload_error,
                        "video_url": video_url,
                        "total_count": 0
                    }
                }

            # 从 state 获取视频文件对象
            from src.utils.llm_invoker import get_video_file_from_state
            video_file = get_video_file_from_state(state)
            if not video_file:
                error_msg = "无法获取视频文件对象"
                logger.error(error_msg)
                return {
                    "inspiration_points": [],
                    "metadata": {
                        "error": error_msg,
                        "video_upload_error": video_upload_error,
                        "video_url": video_url,
                        "total_count": 0
                    }
                }

            # Step 1: 识别候选灵感点（逐帧分析视频）
            logger.info("\n[Step 1/6] 识别候选灵感点（逐帧分析视频）...")
            candidate_points_list = self._step2_identify_candidates(
                text_data, video_file
            )
            candidate_count = len(candidate_points_list)
            logger.info(f"候选灵感点: {candidate_count}个")

            # Step 2: 逆向推导验证
            logger.info("\n[Step 2/6] 逆向推导验证...")
            verified_points_list = self._step3_backward_verification(
                text_data, video_file, candidate_points_list
            )
            verified_count = len(verified_points_list)
            logger.info(f"验证通过: {verified_count}个")

            # Step 3: 去重检查
            logger.info("\n[Step 3/6] 去重检查...")
            deduplicated_points_list = self._step4_deduplication_check(verified_points_list, candidate_points_list)
            deduplicated_count = len(deduplicated_points_list)
            removed_count = verified_count - deduplicated_count
            logger.info(f"去重后: {deduplicated_count}个 (移除{removed_count}个)")

            # Step 4: 三维度评分
            logger.info("\n[Step 4/6] 三维度评分...")
            scored_points_list = self._step5_scoring(
                deduplicated_points_list,
                candidate_points_list,  # 传递原始候选点数据以便查找完整信息
                video_file
            )
            scored_count = len(scored_points_list)
            logger.info(f"评分完成: {scored_count}个")

            # Step 5: 排序筛选
            logger.info("\n[Step 5/6] 排序筛选...")
            filtered_points_list = self._step6_sort_and_filter(scored_points_list)
            total_filtered_count = len(filtered_points_list)
            logger.info(f"筛选后剩余{total_filtered_count}个灵感点")

            # Step 6: 组装最终输出
            logger.info("\n[Step 6/6] 组装最终输出...")
            final_points_list = self._step7_assemble_output(
                filtered_points_list, candidate_points_list, verified_points_list
            )
            total_final_count = len(final_points_list)
            logger.info(f"最终输出{total_final_count}个高质量灵感点")

            # 构建最终输出
            final_output = {
                "inspiration_points": final_points_list,
                "metadata": {
                    "total_count": total_final_count,
                    "perspective": "创作者视角",
                    "analysis_mode": "视频逐帧分析",
                    "process_summary": {
                        "candidate_count": candidate_count,
                        "verified_count": verified_count,
                        "deduplicated_count": deduplicated_count,
                        "scored_count": scored_count,
                        "filtered_count": total_filtered_count,
                        "final_count": total_final_count
                    }
                }
            }

            logger.info(f"最终结果: {total_final_count}个灵感点")

            return final_output

        except Exception as e:
            logger.error(f"灵感点分析失败: {e}", exc_info=True)
            return {
                "inspiration_points": [],
                "metadata": {
                    "error": str(e),
                    "total_count": 0
                }
            }

    # ==================== 7个步骤方法 ====================

    def _step2_identify_candidates(
        self,
        text_data: Dict[str, Any],
        video_file: "genai.types.File"
    ) -> List[Dict[str, Any]]:
        """Step 1: 识别候选灵感点（逐帧分析视频）

        输入：当前视频URI、文本信息
        输出：候选灵感点列表

        策略：
        1. 对视频进行逐帧或关键帧分析
        2. 从视频画面、动作、场景、对话等维度识别灵感点
        """
        prompt = f"""# 任务：识别候选灵感点（逐帧分析视频）

## 当前视频
请对视频进行逐帧或关键帧分析。

**重要**：你需要对视频进行逐帧或关键帧分析，捕捉以下维度的信息：
- 画面内容：场景、物体、人物、环境等视觉元素
- 动作变化：人物的动作、物体的运动、场景的转换
- 时间线索：视频的时间顺序、节奏变化、关键时间点
- 视觉细节：颜色、光影、构图、视觉焦点等
- 音频信息（如有）：对话、音效、背景音乐等

## 核心概念（统一说明，避免重复）

### 字段定义
**分类**（维度）：创作者接收外界信息刺激的角度或通道
- 格式：2-4个字，简洁直观，避免抽象表述

**灵感点**：创作前遇到的、触发创作冲动的客观刺激源（作者被动接收的信息：看到的、听说的、发现的、观察到的、感知到的）
- 格式：不超过15个字，使用自然、通俗、口语化的表达方式
- 本质：刺激源的内容本身（讲什么、做什么、说什么），而非表达手段（如何呈现、如何表达）
- 表达要求：
  * 使用日常生活中的自然语言，避免学术化、抽象化的词汇堆砌
  * 优先使用"的"字短语结构（如"夏日的热闹景象"）或动宾短语（如"观察到的自然互动"）
  * 禁止使用多个抽象名词连用（如"具象化动态互动自然拟人"）
  * 表达要让普通人一看就懂，不需要"翻译"

**描述**：对刺激源本身是什么的详细说明，讲清楚这个灵感点具体指的是什么
- 内容要求：描述刺激源的具体特征、形态、场景、内容等客观信息，让读者能清晰理解"这个灵感点到底是什么东西"
- 注意区分：刺激源内容本身 vs 呈现方式/表现形式

**推理**：说明这个灵感点是如何得出来的，解释从视频内容推导出这个灵感点的过程
- 内容要求：连贯的描述性文字，说明基于视频的哪些画面/动作/场景/对话等线索，推导出创作者接收到了这个刺激源

### 严格禁止（适用于灵感点和描述）
- 不描述创作者如何运用/展现/表达这个刺激，不使用推理性词汇
- 不能是创作形式、表现手法、表达方式、呈现方式、风格、格式等创作应用层面的东西
- 必须是被动接收的刺激，不能是主动创造的内容
- 不解释创作者为什么被触发、如何使用这个刺激
- 不进行主观推理和价值判断
- 禁止词汇堆砌，避免多个抽象概念连用

### 反思验证（识别每个灵感点后必须进行）
- 这个灵感点描述的是内容本身，还是仅仅描述表达手段？
- 如果仅描述表达手段（如呈现方式、创作形式、风格、格式等），应重新提炼真正的刺激源内容
- 这个表达是否符合日常口语习惯？是否通俗易懂？
- 如果描述的是内容本身且表达自然 → 保留

### 识别要求
- **独立性**: 不同灵感点必须是不同的刺激源，本质相同的只保留一个
- **完整性**: 分类→灵感点→描述应形成从刺激通道到刺激内容的完整表达
- **真实性**: 从"作者接收到了什么刺激"（被动接收）而非"作者如何创作"（主动输出）的角度出发
- **视频特性**: 需要关注视频的动态特性，包括画面变化、动作序列、时间线索等

## 输出格式（JSON）
{{
  "候选灵感点列表": [
      {{
        "候选编号": 1,
        "分类": "...",
        "灵感点": "...",
        "描述": "...",
        "推理": "..."
      }}
  ]
}}

**重要提醒**：
- 推理必须是连贯的描述性文字，说明为什么这个灵感点能刺激创作
- 候选编号全局唯一（连续编号：1, 2, 3...）
- 必须基于视频的实际内容进行分析，不能凭空想象"""


        # 将系统提示词合并到用户提示词中
        full_prompt = f"{self.system_prompt}\n\n{prompt}"
        
        result = LLMInvoker.safe_invoke_video_analysis(
            "Step1-识别候选灵感点（视频分析）",
            video_file,
            full_prompt,
            agent=self,
            fallback={}
        )

        return result.get("候选灵感点列表", [])

    def _step4_deduplication_check(
        self,
        verified_points_list: List[Dict[str, Any]],
        candidate_points_list: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """Step 4: 灵感点去重检查 - 识别并移除本质相同的灵感点

        输入：
            - verified_points_list: 验证通过的灵感点列表（包含候选编号和推导说明）
            - candidate_points_list: 原始候选灵感点列表（包含完整字段）
        输出：去重后的验证通过的灵感点列表

        策略：
        1. 从原始候选点中获取完整信息
        2. 只对验证通过的点进行去重分析
        3. 识别本质相同的灵感点组
        4. 每组只保留最精准的一个
        """
        # 构建候选点映射表：编号 -> 完整候选点数据
        candidate_map = {}
        for candidate in candidate_points_list:
            candidate_num = candidate.get("候选编号")
            if candidate_num is not None:
                candidate_map[candidate_num] = candidate

        # 合并所有验证通过的点，并从原始候选点中获取完整信息
        all_verified = []
        for verified_point in verified_points_list:
            candidate_num = verified_point.get("候选编号")
            if candidate_num in candidate_map:
                candidate_data = candidate_map[candidate_num]
                all_verified.append({
                    "candidate_num": candidate_num,
                    "data": candidate_data,
                    "推导说明": verified_point.get("推导说明", "")
                })

        if len(all_verified) <= 1:
            # 只有0或1个验证通过的点，无需去重
            return verified_points_list

        # 构建候选点列表文本（不包含分类标签，只传递灵感点和描述）
        candidates_text = "\n".join([
            f"{p['candidate_num']}. "
            f"灵感点: {p['data'].get('灵感点', '')}\n"
            f"   描述: {p['data'].get('描述', '')}"
            for p in all_verified
        ])

        prompt = f"""# 任务：灵感点去重

## 候选灵感点列表
{candidates_text}

## 核心判断标准

**唯一标准：是否指向同一个刺激源？**

- 如果两个灵感点描述的是**同一个事物/现象/刺激**，只是从不同角度、不同层面、不同粒度来表达 → 重复，去重

## 判断方法

1. **识别刺激源本质**：每个灵感点背后的刺激源到底是什么？
2. **对比刺激源**：是同一个刺激源的不同表述？还是两个不同的刺激源？
3. **忽略表述差异**：不要被具体用词、分类标签、角度差异迷惑，抓住本质

## 保留规则（重复时选择）

从重复组中选择**最能直接反映刺激源本身**的那一个：
- 优先保留描述刺激源本身的（客体）
- 其次保留最具体、最精准的

## 输出（JSON）
{{
  "去重分析": [
    {{"重复组": [2, 3], "重复原因": "两者都在描述同一个刺激源：[具体说明]", "保留编号": 2, "保留理由": "[原因]"}}
  ],
  "保留的候选编号列表": [1, 2]
}}

## 原则

- 宁可过度去重，不遗漏重复
- 抓住本质，忽略表面差异
- 不确定时 → 判定重复 → 去重"""

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

        result = LLMInvoker.safe_invoke(
            self,
            "Step4-去重检查",
            messages,
            fallback={"保留的候选编号列表": [p['candidate_num'] for p in all_verified]}
        )

        # 提取保留的候选编号
        kept_numbers = set(result.get("保留的候选编号列表", []))

        # 记录去重分析结果
        dedup_analysis = result.get("去重分析", [])
        if dedup_analysis:
            logger.info(f"发现{len(dedup_analysis)}组重复:")
            for group in dedup_analysis:
                logger.info(f"  - 重复组 {group.get('重复组', [])}: {group.get('重复原因', '')}")
                logger.info(f"    保留: 候选{group.get('保留编号', '')} - {group.get('保留理由', '')}")

        # 构建去重后的列表（保持验证通过的点的结构）
        deduplicated_list = []
        for verified_point in verified_points_list:
            candidate_num = verified_point.get("候选编号")
            if candidate_num in kept_numbers:
                # 保留候选编号和推导说明
                deduplicated_list.append(verified_point)

        return deduplicated_list

    def _step3_backward_verification(
        self,
        text_data: Dict[str, Any],
        video_file: "genai.types.File",
        candidate_points_list: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """Step 2: 逆向推导验证

        输入：候选灵感点列表、当前视频
        输出：验证通过的灵感点列表

        优化：LLM只输出编号和验证结果，代码实现关联
        """
        if not candidate_points_list:
            return []

        # 将候选点转换为文本
        candidates_text = "\n".join([
            f"{p.get('候选编号', i + 1)}. 分类: {p.get('分类', '')}, 灵感点: {p.get('灵感点', '')}"
            for i, p in enumerate(candidate_points_list)
        ])

        prompt = f"""# 任务：逆向推导验证

## 当前视频
请对视频进行逐帧或关键帧分析。
{self._build_video_text_info(text_data)}

## 候选灵感点
{candidates_text}

## 验证要求
对每个候选灵感点进行逆向推导验证：

**验证问题**：
- 从这个灵感点出发，能否推导出整个视频的内容？
- 这个刺激源是创作前的触发，而不是创作目的？
- 这个灵感点是否真正捕捉到了深层动机？

**验证规则**：
- 能推导出 → 验证通过（true）
- 不能推导出或逻辑不通 → 验证失败（false）

## 输出格式（JSON - 简化版）
{{
  "验证结果列表": [
    {{
      "候选编号": 1,
      "验证结果": true,
      "推导说明": "从这个灵感点可以推导出..."
    }},
    {{
      "候选编号": 2,
      "验证结果": false,
      "推导说明": "无法从这个灵感点推导出..."
    }}
  ]
}}

**格式说明**：
- 候选编号：整数，对应上面的候选灵感点编号（1, 2, 3...）
- 验证结果：布尔值（true或false）
- 推导说明：能否从这个灵感点推导出视频内容的详细说明

**重要**：只输出编号、验证结果和推导说明，不需要复制其他字段"""

        # 使用新的视频分析API
        # 将系统提示词合并到用户提示词中
        full_prompt = f"{self.system_prompt}\n\n{prompt}"
        
        result = LLMInvoker.safe_invoke_video_analysis(
            "Step2-逆向验证（视频分析）",
            video_file,
            full_prompt,
            agent=self,
            fallback={}
        )

        # 使用辅助方法关联验证结果
        verification_results = result.get("验证结果列表", [])
        verified_points_list = self._associate_verification_results(
            candidate_points_list,
            verification_results
        )

        return verified_points_list

    def _step5_scoring(
        self,
        verified_points_list: List[Dict[str, Any]],
        candidate_points_list: List[Dict[str, Any]] = None,
        video_file: "genai.types.File" = None
    ) -> List[Dict[str, Any]]:
        """Step 4: 三维度评分

        输入：验证通过的灵感点列表、原始候选点列表
        输出：评分后的灵感点列表

        优化：LLM只输出编号和评分，代码实现关联
        """
        if not verified_points_list:
            return []

        # 构建候选点映射：编号 -> 完整候选点数据（用于查找详细信息）
        candidate_map = {}
        if candidate_points_list:
            for candidate in candidate_points_list:
                candidate_num = candidate.get("候选编号")
                if candidate_num is not None:
                    candidate_map[candidate_num] = candidate

        # 构建评分用的灵感点文本（从原始候选点中查找完整信息）
        points_list = []
        for p in verified_points_list:
            candidate_num = p.get('候选编号')
            # 从原始候选点中查找完整信息
            candidate_data = candidate_map.get(candidate_num, {})
            分类 = candidate_data.get('分类', '')
            灵感点 = candidate_data.get('灵感点', '')
            描述 = candidate_data.get('描述', '')
            points_list.append(f"{candidate_num}. 分类: {分类}, 灵感点: {灵感点}\n   描述: {描述}")

        points_text = "\n".join(points_list)

        prompt = f"""# 任务：评估灵感点

## 待评分的灵感点
{points_text}

## 评分标准

对每个灵感点从三个维度评分（每个维度1-10分）：

### 1. 人设契合度（默认先都给5分）
- 因没有历史视频信息无法推算人设，先都给5分

### 2. 触发可能性（1-10分）
- 评估这个刺激点触发创作的可能性有多大
- 考虑刺激点的吸引力强度
- 考虑创作者遇到这个刺激时产生创作冲动的概率

### 3. 内容解释力（1-10分）
- 评估这个刺激点能否解释视频的核心内容
- 判断从这个刺激点能否推导出视频的主要内容
- 考虑刺激点与视频内容的关联强度

**总分 = 人设契合度 + 触发可能性 + 内容解释力（范围：3-30分）**

## 评分原则
- 客观评分，不要预设分数高低
- 如果与视频内容不符，触发可能性也会很低
- 如果是视频核心特征，触发可能性反而很高

## 输出格式（JSON）
{{
  "评分结果列表": [
    {{
      "候选编号": 1,
      "人设契合度": 8,
      "触发可能性": 7,
      "内容解释力": 9,
      "总分": 24,
      "评分说明": "详细说明评分理由：为什么这个刺激点符合人设、触发创作的可能性如何、能否解释视频内容"
    }}
  ]
}}

**要求**：
- 候选编号：整数，对应待评分灵感点的编号
- 人设契合度、触发可能性、内容解释力：1-10的整数
- 总分：3-30的整数（三个评分之和）
- 评分说明：详细说明评分理由
- 只输出编号和评分信息，不需要复制其他字段"""

        # 使用新的视频分析API
        # 将系统提示词合并到用户提示词中
        full_prompt = f"{self.system_prompt}\n\n{prompt}"
        
        result = LLMInvoker.safe_invoke_video_analysis(
            "Step4-评分（视频分析）",
            video_file,
            full_prompt,
            agent=self,
            fallback={}
        )

        # 使用辅助方法关联评分结果
        scoring_results = result.get("评分结果列表", []) if isinstance(result, dict) else result
        scored_points_list = self._associate_scoring_results(
            verified_points_list,
            scoring_results
        )

        return scored_points_list


    def _step6_sort_and_filter(
        self,
        scored_points_list: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """Step 6: 排序筛选

        输入：评分后的灵感点列表（仅包含候选编号和评分）
        输出：筛选后的灵感点列表（仅包含候选编号和评分）

        筛选策略：
        1. 按总分降序排序
        2. 筛选规则：
           - 优先保留总分 >= MIN_QUALITY_SCORE (15分) 的灵感点
           - 如果都没有达标的，只保留1个分数最高的
           - 最终输出最多2个灵感点（选择分数最高的2个）
        """
        if not scored_points_list:
            return []

        # 按总分降序排序
        sorted_points = sorted(
            scored_points_list,
            key=lambda x: (x.get("总分", 0), x.get("人设契合度", 0)),
            reverse=True
        )

        # 筛选：总分 >= MIN_QUALITY_SCORE
        qualified_points = [p for p in sorted_points if p.get("总分", 0) >= MIN_QUALITY_SCORE]

        if not qualified_points:
            # 没有达标的，保底保留全局最高分的1个
            if sorted_points:
                best_point = sorted_points[0]
                logger.warning(
                    f"无达标灵感点，保底保留全局最高分: "
                    f"总分={best_point.get('总分', 0)}"
                )
                return [best_point]
            else:
                logger.warning("没有候选灵感点")
                return []
        else:
            logger.info(f"共{len(qualified_points)}个达标灵感点（总分>={MIN_QUALITY_SCORE}）")

        # 最多保留2个
        top_points = qualified_points[:2]

        logger.info(f"最终保留最多2个灵感点，实际保留{len(top_points)}个")
        scores = [p.get("总分", 0) for p in top_points]
        logger.info(f"  总分: {scores}")

        # 统计最终结果
        qualified_count = sum(
            1 for p in top_points
            if p.get("总分", 0) >= MIN_QUALITY_SCORE
        )

        logger.info(
            f"筛选后剩余{len(top_points)}个灵感点 "
            f"(其中{qualified_count}个总分>={MIN_QUALITY_SCORE})"
        )

        return top_points

    def _step7_assemble_output(
        self,
        filtered_points_list: List[Dict[str, Any]],
        candidate_points_list: List[Dict[str, Any]],
        verified_points_list: List[Dict[str, Any]] = None
    ) -> List[Dict[str, Any]]:
        """Step 7: 组装最终输出（从原始数据中提取字段）

        输入：
            - filtered_points_list: 筛选后的灵感点列表（包含候选编号、评分、评分说明）
            - candidate_points_list: 原始候选灵感点列表（包含所有详细字段）
            - verified_points_list: 验证结果列表（包含推导说明） - 可选
        输出：最终灵感点列表（完整字段 + 验证信息 + 评分信息）

        组装策略：
        1. 从原始候选点中提取完整字段
        2. 从验证结果中提取推导说明
        3. 从筛选结果中提取评分信息
        4. 保持筛选后的排序
        """
        # 构建候选点映射表：编号 -> 完整候选点数据
        candidate_map = {}
        for candidate in candidate_points_list:
            candidate_num = candidate.get("候选编号")
            if candidate_num is not None:
                candidate_map[candidate_num] = candidate

        # 构建验证信息映射表：编号 -> 推导说明（如果提供了verified_points_list）
        verification_map = {}
        if verified_points_list:
            for verified_point in verified_points_list:
                candidate_num = verified_point.get("候选编号")
                if candidate_num is not None:
                    verification_map[candidate_num] = verified_point.get("推导说明", "")

        final_points_list = []

        # 从原始候选点和筛选结果中组装完整数据
        for filtered_point in filtered_points_list:
            candidate_num = filtered_point.get("候选编号")
            original_data = candidate_map.get(candidate_num, {})

            # 组装最终输出格式
            formatted_point = {
                "候选编号": candidate_num,
                "分类": original_data.get("分类", ""),
                "灵感点": original_data.get("灵感点", ""),
                "描述": original_data.get("描述", ""),
                "推理": original_data.get("推理", "")
            }

            # 添加验证信息（推导说明）
            if verification_map:
                formatted_point["推导说明"] = verification_map.get(candidate_num, "")

            # 添加评分信息（重构为scoring对象）
            formatted_point["scoring"] = {
                "人设契合度": filtered_point.get("人设契合度", 0),
                "触发可能性": filtered_point.get("触发可能性", 0),
                "内容解释力": filtered_point.get("内容解释力", 0),
                "总分": filtered_point.get("总分", 0),
                "评分说明": filtered_point.get("评分说明", "")
            }

            final_points_list.append(formatted_point)

        logger.info(f"最终组装完成，共{len(final_points_list)}个灵感点")

        return final_points_list

    def _build_messages(self, state: Dict[str, Any]) -> List[Dict[str, Any]]:
        """构建消息 - BaseLLMAgent要求实现（本Agent不使用此方法）"""
        return []

    def _update_state(self, state: Dict[str, Any], response: Any) -> Dict[str, Any]:
        """更新状态 - BaseLLMAgent要求实现（本Agent不使用此方法）"""
        return state

    def _associate_verification_results(
        self,
        candidate_points_list: List[Dict[str, Any]],
        verification_results: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """关联验证结果到候选点

        Args:
            candidate_points_list: 候选灵感点列表
            verification_results: 验证结果列表（包含候选编号、验证结果、推导说明）

        Returns:
            验证通过的灵感点列表（仅保留候选编号用于后续关联）
        """
        # 构建验证映射表：编号 -> 验证结果
        verification_map = {}
        for v in verification_results:
            candidate_num = v.get("候选编号")
            if candidate_num is not None:
                verification_map[candidate_num] = {
                    "验证结果": v.get("验证结果", False),
                    "推导说明": v.get("推导说明", "")
                }

        # 关联候选点信息：只返回验证通过的
        verified_points_list = []
        total_candidates = len(candidate_points_list)
        total_verified = 0

        for candidate in candidate_points_list:
            candidate_num = candidate.get("候选编号")
            # 获取该候选点的验证结果
            verification = verification_map.get(candidate_num, {})

            if verification.get("验证结果", False):
                # 保留候选编号和推导说明用于后续关联
                verified_point = {
                    "候选编号": candidate_num,
                    "推导说明": verification.get("推导说明", "")
                }
                verified_points_list.append(verified_point)
                total_verified += 1

        logger.info(f"验证通过率: {total_verified}/{total_candidates}")

        return verified_points_list

    def _associate_scoring_results(
        self,
        verified_points_list: List[Dict[str, Any]],
        scoring_results: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """关联评分结果到验证通过的灵感点

        Args:
            verified_points_list: 验证通过的灵感点列表
            scoring_results: 评分结果列表（包含候选编号、三维度评分、总分、评分说明）

        Returns:
            评分后的灵感点列表（仅保留候选编号和评分信息）
        """
        # 构建评分映射表：编号 -> 评分信息
        scoring_map = {}
        for s in scoring_results:
            candidate_num = s.get("候选编号")
            if candidate_num is not None:
                scoring_map[candidate_num] = {
                    "人设契合度": s.get("人设契合度", 0),
                    "触发可能性": s.get("触发可能性", 0),
                    "内容解释力": s.get("内容解释力", 0),
                    "总分": s.get("总分", 0),
                    "评分说明": s.get("评分说明", "")
                }

        # 关联灵感点信息：只保留候选编号和评分
        scored_points_list = []
        all_scores = []

        for point in verified_points_list:
            candidate_num = point.get("候选编号")
            # 获取该候选点的评分结果
            scoring = scoring_map.get(candidate_num, {
                "人设契合度": 0,
                "触发可能性": 0,
                "内容解释力": 0,
                "总分": 0,
                "评分说明": "评分失败"
            })

            # 只保留候选编号和评分信息
            scored_point = {
                "候选编号": candidate_num,
                "人设契合度": scoring["人设契合度"],
                "触发可能性": scoring["触发可能性"],
                "内容解释力": scoring["内容解释力"],
                "总分": scoring["总分"],
                "评分说明": scoring["评分说明"]
            }

            scored_points_list.append(scored_point)
            all_scores.append(scoring["总分"])

        # 日志输出评分统计
        if all_scores:
            avg_score = sum(all_scores) / len(all_scores)
            logger.info(f"评分统计: 平均分={avg_score:.1f}, 最高分={max(all_scores)}, 最低分={min(all_scores)}")

        return scored_points_list

    def _build_video_text_info(self, text_data: Dict[str, Any]) -> str:
        """构建视频文本信息（视频分析版本）
        
        Args:
            text_data: 包含标题和正文的字典
            
        Returns:
            格式化的视频文本信息
        """
        parts = []
        if text_data.get("title"):
            parts.append(f"标题: {text_data['title']}")
        if text_data.get("body"):
            parts.append(f"正文: {text_data['body']}")
        if text_data.get("hashtags"):
            parts.append(f"话题标签: {' '.join(text_data['hashtags'])}")
        return "\n".join(parts) if parts else "无文本内容"

