#!/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


class KeyPointsAgent(BaseLLMAgent):

    def __init__(
        self,
        name: str = "key_points_agent",
        description: str = "关键点提取Agent - 消费者视角提取核心吸引点",
        model_provider: str = "google_genai",
        temperature: float = 0.3,
        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:
        """构建系统提示词 - 只包含所有任务共享的核心信息"""
        return """你是内容分析专家，从消费者视角提取核心吸引点。

# 关键点定义
消费者视角下能够引起关注的核心要素

# 维度定义（有限集合 - 二级分类）
关键点必须归属于以下维度体系：

## 维度大类：形式
**细分维度：**
1. **风格** - 关注内容的整体呈现方式、结构、格式等外在特征
2. **关系** - 关注内容中各个实质要素之间的关联、逻辑关系

## 维度大类：实质
**细分维度：**
1. **元素** - 关注内容中的具体实体、对象、要素等
2. **分类** - 关注对具体元素的抽象、总结、归纳

# 关键点层级关系
关键点之间存在层级关系，形成树形结构：
- **层级判断标准**：
  - 大类 → 小类
  - 抽象 → 具体
  - 整体 → 局部
- **层级表达**：一级关键点 → 二级关键点 → 三级关键点（支持多层嵌套）
- **层级原则**：子关键点是父关键点的细分、具体化或局部

# 核心原则
- 消费者视角：思考"什么吸引了消费者"
- 客观性：只陈述事实，不做主观判断
- 视频逐帧分析：需要对视频进行逐帧或关键帧分析，捕捉画面、动作、场景、对话等维度的吸引点
- 二级维度归类：必须明确标注维度大类（形式/实质）和维度细分（风格/关系/元素/分类）
- 关键点精准：不超过15个字，只写核心发现（不写如何呈现）
- 描述充分：详细解释关键点的含义和价值
- 层级清晰：识别关键点之间的层级关系，构建树形结构
"""

    def process(self, state: Dict[str, Any], config=None) -> Dict[str, Any]:
        """处理状态 - 消费者视角提取关键点（多步骤：识别 → 两阶段去重 → 层级构建 → 输出）

        流程：
        1. 识别候选关键点
        2. 第一轮去重：与灵感点、目的点去重
        3. 第二轮去重：关键点之间去重
        4. 构建层级关系：将扁平关键点组织成树形结构
        5. 组装最终输出
        """
        if not self.is_initialized:
            self.initialize()

        logger.info("关键点分析开始")

        try:
            # 从 state 获取视频文件对象
            from src.utils.llm_invoker import get_video_file_from_state
            video_file = get_video_file_from_state(state)

            if not video_file:
                logger.warning("无法获取视频文件对象，跳过关键点提取")
                return {
                    "key_points": {
                        "error": "无法获取视频文件对象",
                        "key_points": [],
                        "total_count": 0,
                        "root_count": 0
                    }
                }

            # 提取灵感点和目的点信息（用于去重）
            inspiration_points_data = self._extract_inspiration_points(state)
            purpose_point_data = self._extract_purpose_point(state)

            # Step 1: 识别候选关键点
            logger.info("\n[Step 1/5] 识别候选关键点（视频逐帧分析）...")
            candidate_points = self._step1_identify_candidates(
                video_file, inspiration_points_data, purpose_point_data
            )
            candidate_count = len(candidate_points)
            logger.info(f"候选关键点: {candidate_count}个")

            # Step 2: 第一轮去重 - 与灵感点、目的点去重
            logger.info("\n[Step 2/5] 第一轮去重 - 与灵感点、目的点去重...")
            after_external_dedup = self._step2_external_deduplication(
                candidate_points, inspiration_points_data, purpose_point_data
            )
            after_external_count = len(after_external_dedup)
            external_removed = candidate_count - after_external_count
            logger.info(f"第一轮去重: {after_external_count}个 (移除{external_removed}个)")

            # Step 3: 第二轮去重 - 关键点之间去重
            logger.info("\n[Step 3/5] 第二轮去重 - 关键点之间去重...")
            after_internal_dedup = self._step3_internal_deduplication(after_external_dedup)
            after_internal_count = len(after_internal_dedup)
            internal_removed = after_external_count - after_internal_count
            logger.info(f"第二轮去重: {after_internal_count}个 (移除{internal_removed}个)")

            # Step 4: 构建层级关系
            logger.info("\n[Step 4/5] 构建层级关系...")
            hierarchical_points = self._step4_build_hierarchy(after_internal_dedup)
            logger.info(f"构建层级: {len(hierarchical_points)}个一级节点")

            # Step 5: 组装最终输出
            logger.info("\n[Step 5/5] 组装最终输出...")
            final_output = {
                "key_points": {
                    "key_points": hierarchical_points,
                    "total_count": after_internal_count,
                    "root_count": len(hierarchical_points)
                }
            }

            logger.info(f"最终结果: {after_internal_count}个关键点, {len(hierarchical_points)}个一级节点")

            return final_output

        except Exception as e:
            logger.error(f"关键点分析失败: {e}", exc_info=True)
            return {
                "key_points": {
                    "error": str(e),
                    "key_points": [],
                    "total_count": 0,
                    "root_count": 0
                }
            }

    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 _extract_inspiration_points(self, state: Dict[str, Any]) -> List[Dict[str, Any]]:
        """从state中提取灵感点信息（用于去重）"""
        if "inspiration_points" not in state:
            return None

        inspiration_data = state.get("inspiration_points")
        
        # 新结构：inspiration_points 直接是列表
        if isinstance(inspiration_data, list):
            if inspiration_data:
                logger.info(f"检测到 {len(inspiration_data)} 个灵感点，将用于去重检查")
                # 提取核心字段用于去重
                return [
                    {
                        "分类": p.get("分类", ""),
                        "灵感点": p.get("灵感点", ""),
                        "描述": p.get("描述", "")
                    }
                    for p in inspiration_data
                ]
            return None

        # 兼容旧结构：{"inspiration_points": {"points": [...]}}
        if isinstance(inspiration_data, dict):
            points = inspiration_data.get("points", [])
            if points:
                logger.info(f"检测到 {len(points)} 个灵感点（旧格式），将用于去重检查")
                return points

        return None

    def _extract_purpose_point(self, state: Dict[str, Any]) -> Dict[str, Any]:
        """从state中提取目的点信息（用于去重）"""
        if "purpose_point" not in state:
            return None

        purpose_data = state.get("purpose_point", {})

        if not isinstance(purpose_data, dict):
            return None

        # 新结构：{"purpose_point": {"perspective": "创作者视角", "purposes": [...], "total_count": 2}}
        if "purposes" in purpose_data:
            purposes = purpose_data.get("purposes", [])
            if purposes:
                logger.info(f"检测到 {len(purposes)} 个目的点，将用于去重检查")
                return purpose_data

        # 旧结构（兼容）：{"purpose_point": {"main_purpose": {"维度": "", "目的点": "", "描述": ""}, ...}}
        if "main_purpose" in purpose_data:
            main_purpose = purpose_data.get("main_purpose", {})
            if isinstance(main_purpose, dict) and "目的点" in main_purpose:
                logger.info(f"检测到目的点（旧结构）: {main_purpose.get('目的点', '')}, 将用于去重检查")
                return purpose_data

        return None

    # ==================== 4个步骤方法 ====================

    def _step1_identify_candidates(
        self,
        video_file: "genai.types.File",
        inspiration_points_data: List[Dict[str, Any]] = None,
        purpose_point_data: Dict[str, Any] = None
    ) -> List[Dict[str, Any]]:
        """Step 1: 识别候选关键点（视频逐帧分析）

        输入：视频文件对象、灵感点数据、目的点数据
        输出：候选关键点列表（包含候选编号）

        策略：
        1. 对视频进行逐帧或关键帧分析
        2. 从消费者视角识别所有可能的关键点
        3. 每个候选点分配一个唯一编号
        4. 严格按照有限集合维度分类（形式&关系、实质）
        """
        prompt = self._build_identification_prompt("")

        # 使用新的视频分析API
        # 将系统提示词合并到用户提示词中
        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 _step2_external_deduplication(
        self,
        candidate_points: List[Dict[str, Any]],
        inspiration_points_data: List[Dict[str, Any]] = None,
        purpose_point_data: Dict[str, Any] = None
    ) -> List[Dict[str, Any]]:
        """Step 2: 第一轮去重 - 与灵感点、目的点去重

        输入：候选关键点列表、灵感点数据、目的点数据
        输出：去除与灵感点/目的点重复后的关键点列表

        去重策略：
        1. 与灵感点去重（关键点≠灵感点）
        2. 与目的点去重（关键点≠目的点）
        """
        if not candidate_points:
            return []

        # 如果没有灵感点和目的点数据，直接返回候选点
        if not inspiration_points_data and not purpose_point_data:
            logger.info("无灵感点和目的点数据，跳过第一轮去重")
            return candidate_points

        # 构建候选点列表文本
        candidates_text = "\n".join([
            f"{p.get('候选编号', i + 1)}. "
            f"维度: [{p.get('维度大类', '')}] {p.get('维度细分', '')}, "
            f"关键点: {p.get('关键点', '')}\n"
            f"   描述: {p.get('描述', '')}"
            for i, p in enumerate(candidate_points)
        ])

        # 构建灵感点参考文本
        inspiration_text = ""
        if inspiration_points_data:
            inspiration_items = []
            for p in inspiration_points_data:
                category = p.get("category", "")
                分类 = p.get("分类", "")
                灵感点 = p.get("灵感点", "")
                描述 = p.get("描述", "")
                inspiration_items.append(f"- [{category}] 分类: {分类}, 灵感点: {灵感点}, 描述: {描述}")
            inspiration_text = "\n".join(inspiration_items)

        # 构建目的点参考文本
        purpose_text = ""
        if purpose_point_data:
            # 新结构：{"purposes": [...]}
            if "purposes" in purpose_point_data:
                purposes = purpose_point_data.get("purposes", [])
                purpose_items = []
                for p in purposes:
                    维度 = p.get("维度", {})
                    if isinstance(维度, dict):
                        维度_str = f"{维度.get('一级分类', '')}/{维度.get('二级分类', '')}"
                    else:
                        维度_str = str(维度)
                    目的点 = p.get("目的点", "")
                    描述 = p.get("描述", "")
                    purpose_items.append(f"- 维度: {维度_str}, 目的点: {目的点}, 描述: {描述}")
                purpose_text = "\n".join(purpose_items)
            # 旧结构（兼容）：{"main_purpose": {...}}
            elif "main_purpose" in purpose_point_data:
                main_purpose = purpose_point_data.get("main_purpose", {})
                维度 = main_purpose.get("维度", "")
                目的点 = main_purpose.get("目的点", "")
                描述 = main_purpose.get("描述", "")
                purpose_text = f"维度: {维度}\n目的点: {目的点}\n描述: {描述}"

        prompt = f"""# 任务：与灵感点、目的点去重

## 候选关键点
{candidates_text}

## 灵感点（创作触发源）
{'以下是已识别的灵感点：' if inspiration_text else '无灵感点'}
{inspiration_text if inspiration_text else ''}

## 目的点（创作目标）
{'以下是已识别的目的点：' if purpose_text else '无目的点'}
{purpose_text if purpose_text else ''}

## 去重规则

**核心原则**：关键点与灵感点/目的点重合 → 必须移除关键点

**判断方法**：
1. 角色检查：关键点应是"消费吸引点"，不应是"触发源"或"创作目标"
2. 语义去重：移除修饰词，提取核心概念对比 → 核心概念相同 → 移除关键点

**重要**：只要存在重合（角色错误或核心概念相同），就移除关键点

## 输出（JSON）
{{
  "去重分析": [{{"去重类型": "与灵感点去重|与目的点去重", "重复候选编号": 1, "重复原因": "说明"}}],
  "保留的候选编号列表": [1, 2, 3]
}}"""

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

        result = LLMInvoker.safe_invoke(
            self,
            "Step2-与灵感点目的点去重",
            messages,
            fallback={"保留的候选编号列表": [p.get('候选编号', i+1) for i, p in enumerate(candidate_points)]}
        )

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

        # 记录去重分析结果
        dedup_analysis = result.get("去重分析", [])
        if dedup_analysis:
            logger.info(f"发现{len(dedup_analysis)}个需要移除的候选:")
            for item in dedup_analysis:
                dedup_type = item.get('去重类型', '')
                重复候选编号 = item.get('重复候选编号', '')
                重复原因 = item.get('重复原因', '')
                logger.info(f"  - [{dedup_type}] 候选{重复候选编号}: {重复原因}")

        # 构建去重后的列表
        deduplicated_points = []
        for point in candidate_points:
            candidate_num = point.get("候选编号")
            if candidate_num in kept_numbers:
                deduplicated_points.append(point)

        return deduplicated_points

    def _step3_internal_deduplication(
        self,
        candidate_points: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """Step 3: 第二轮去重 - 关键点之间去重

        输入：第一轮去重后的关键点列表
        输出：关键点之间去重后的最终列表

        去重策略：
        本质相同的关键点只保留一个
        """
        if not candidate_points:
            return []

        # 如果只有1个候选点，无需去重
        if len(candidate_points) == 1:
            logger.info("只有1个候选点，无需关键点之间去重")
            return candidate_points

        # 构建候选点列表文本
        candidates_text = "\n".join([
            f"{p.get('候选编号', i + 1)}. "
            f"维度: [{p.get('维度大类', '')}] {p.get('维度细分', '')}, "
            f"关键点: {p.get('关键点', '')}\n"
            f"   描述: {p.get('描述', '')}"
            for i, p in enumerate(candidate_points)
        ])

        prompt = f"""# 任务：关键点之间去重

## 候选关键点
{candidates_text}

## 去重规则

**本质相同的定义**：
- 核心发现相同（描述同一个吸引点）
- 包含关系（一个是另一个的具体/一般化）
- 粒度不同（抽象层级不同，但指向同一吸引点）

**判断方法**：移除修饰词 → 提取核心发现 → 核心相同 = 本质相同 → 去重

**保留优先级**（本质相同时）：
1. 具体 > 一般
2. 精准 > 模糊
3. 原始 > 衍生
4. 本质 > 形式

## 输出（JSON）
{{
  "去重分析": [{{"重复组": [1, 5], "重复原因": "说明", "保留编号": 1, "保留理由": "说明"}}],
  "保留的候选编号列表": [1, 2, 3]
}}"""

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

        result = LLMInvoker.safe_invoke(
            self,
            "Step3-关键点之间去重",
            messages,
            fallback={"保留的候选编号列表": [p.get('候选编号', i+1) for i, p in enumerate(candidate_points)]}
        )

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

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

        # 构建去重后的列表
        deduplicated_points = []
        for point in candidate_points:
            candidate_num = point.get("候选编号")
            if candidate_num in kept_numbers:
                deduplicated_points.append(point)

        return deduplicated_points

    def _step4_build_hierarchy(
        self,
        flat_points: List[Dict[str, Any]]
    ) -> List[Dict[str, Any]]:
        """Step 4: 构建层级关系 - 将扁平化关键点组织成树形结构

        输入：扁平化的关键点列表
        输出：具有层级关系的树形结构关键点列表

        层级构建策略：
        1. 识别关键点之间的层级关系（大类→小类、抽象→具体、整体→局部）
        2. 构建树形结构（父节点 → 子节点）
        3. 保持一级关键点作为根节点，子关键点嵌套在children字段中
        """
        if not flat_points:
            return []

        # 如果只有1个关键点，无需构建层级
        if len(flat_points) == 1:
            logger.info("只有1个关键点，无需构建层级关系")
            point = flat_points[0].copy()
            point["children"] = []
            return [point]

        # 构建关键点列表文本
        points_text = "\n".join([
            f"{p.get('候选编号', i + 1)}. "
            f"维度: [{p.get('维度大类', '')}] {p.get('维度细分', '')}, "
            f"关键点: {p.get('关键点', '')}\n"
            f"   描述: {p.get('描述', '')}"
            for i, p in enumerate(flat_points)
        ])

        prompt = f"""# 任务：构建关键点层级关系

## 关键点列表（扁平化）
{points_text}

## 层级判断标准

**层级关系定义**：
- **大类 → 小类**：一个关键点是另一个关键点的具体类别
- **抽象 → 具体**：一个关键点是另一个关键点的具体表现
- **整体 → 局部**：一个关键点是另一个关键点的组成部分

**判断方法**：
1. 识别父子关系：子关键点是父关键点的细分、具体化或局部
2. 构建树形结构：父节点可以有多个子节点，子节点也可以继续有子节点
3. 确保无循环：不能出现A→B→A的情况

**层级原则**：
- 同一层级的关键点应该是并列关系（不存在包含、细分关系）
- 不同层级的关键点应该是从属关系（父→子）
- 一级关键点（根节点）应该是最抽象、最整体的关键点
- 如果两个关键点没有明确的层级关系，则保持在同一层级

## 输出（JSON）
{{
  "层级分析": [
    {{
      "父编号": 1,
      "子编号列表": [2, 3],
      "层级关系": "大类→小类|抽象→具体|整体→局部",
      "关系说明": "说明为什么存在这个层级关系",
      "子节点原因": {{
        "2": "说明编号2作为子节点的具体原因",
        "3": "说明编号3作为子节点的具体原因"
      }}
    }}
  ],
  "一级关键点编号列表": [1, 4, 5]
}}

注意：
- "一级关键点编号列表"：列出所有一级关键点（根节点）的候选编号
- "层级分析"：描述所有父子关系，可以有多层嵌套
- "子节点原因"：为每个子节点说明为什么它是父节点的子节点（细分、具体化、局部的具体原因）
"""

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

        result = LLMInvoker.safe_invoke(
            self,
            "Step4-构建层级关系",
            messages,
            fallback={
                "一级关键点编号列表": [p.get('候选编号', i+1) for i, p in enumerate(flat_points)],
                "层级分析": []
            }
        )

        # 构建编号到关键点的映射
        point_map = {p.get("候选编号"): p.copy() for p in flat_points}
        for point in point_map.values():
            point["children"] = []

        # 记录层级分析
        hierarchy_analysis = result.get("层级分析", [])
        if hierarchy_analysis:
            logger.info(f"识别到{len(hierarchy_analysis)}组父子关系:")
            for item in hierarchy_analysis:
                父编号 = item.get('父编号', '')
                子编号列表 = item.get('子编号列表', [])
                层级关系 = item.get('层级关系', '')
                关系说明 = item.get('关系说明', '')
                logger.info(f"  - 父节点{父编号} → 子节点{子编号列表}")
                logger.info(f"    关系类型: {层级关系}")
                logger.info(f"    说明: {关系说明}")
        else:
            logger.info("未识别到层级关系，所有关键点保持一级")

        # 构建父子关系
        for item in hierarchy_analysis:
            父编号 = item.get('父编号')
            子编号列表 = item.get('子编号列表', [])
            子节点原因 = item.get('子节点原因', {})
            if 父编号 in point_map:
                for 子编号 in 子编号列表:
                    if 子编号 in point_map:
                        child_point = point_map[子编号].copy()
                        # 添加"作为子节点的原因"字段
                        child_point["作为子节点的原因"] = 子节点原因.get(str(子编号), "")
                        point_map[父编号]["children"].append(child_point)

        # 提取一级关键点（根节点）
        root_numbers = set(result.get("一级关键点编号列表", []))
        root_points = [point_map[num] for num in root_numbers if num in point_map]

        # 如果没有识别出一级关键点，则所有点都作为一级关键点
        if not root_points:
            logger.info("未识别出一级关键点，所有关键点作为一级关键点")
            root_points = list(point_map.values())

        return root_points

    def _build_identification_prompt(self, video_uri: str = "") -> str:
        """构建识别候选关键点的prompt（视频逐帧分析版本）"""
        prompt = f"""# 任务：识别候选关键点（视频逐帧分析，扁平化列表）

## 视频内容
请对视频进行逐帧或关键帧分析。

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

## 字段定义

### 维度大类
只能是"形式"或"实质"

### 维度细分
- 形式类：只能是"风格"或"关系"
- 实质类：只能是"元素"或"分类"

### 关键点
≤15字核心发现（只写"是什么"，不写"如何呈现"）

### 描述
- **本质**: 讲清楚这个关键点是什么（吸引点的具体特征、形态、内容等客观信息）
- **内容要求**:
  - 描述吸引点本身的客观特征
  - 说明这个吸引点的完整上下文
  - 让读者能清晰理解"这个关键点到底是什么"
- **严格禁止**:
  - 不解释为什么吸引
  - 不进行价值判断和主观评价
  - 不分析效果和影响

## 维度分类要求（二级分类体系）

**维度大类：形式**
- **风格**：关注内容的整体呈现方式、结构、格式等外在特征
- **关系**：关注内容中各个实质要素之间的关联、逻辑关系

**维度大类：实质**
- **元素**：关注内容中的具体实体、对象、要素等
- **分类**：关注对具体元素的抽象、总结、归纳

## 输出（JSON）
{{
  "候选关键点列表": [
    {{
      "候选编号": 1,
      "维度大类": "形式|实质",
      "维度细分": "风格|关系|元素|分类",
      "关键点": "核心发现",
      "描述": "充分说明"
    }}
  ]
}}

要求：
- 广泛收集：从视频的各个维度（画面、动作、场景、对话等）广泛收集吸引点
- 客观陈述：只陈述事实，不做主观判断
- 严格按照二级维度体系归类：必须明确标注维度大类（形式/实质）和维度细分（风格/关系/元素/分类）
- 编号连续：候选编号从1开始连续编号
- 基于视频：所有关键点必须基于视频的实际内容，不能凭空想象

注意：此步骤输出扁平化列表，后续会构建层级关系
"""

        return prompt



