#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
形式提取Agent (ScriptFormExtractionAgent) - 视频版

功能：
- 从视频内容中提取形式元素（具象概念形式、具体元素形式、整体形式）
- Step 1: 提取具象概念形式（画面文字 + 口播内容的表达方式）
- Step 2: 提取具体元素形式（视频中的视觉呈现+映射，包括独立描述和映射关系）
- Step 3: 提取整体形式（多模态组合，包括画面结构 + 叙事节奏 + 运镜等）
- Step 4: 共性分析（当前省略）
- Step 5: 多维度评分（vs 形式关键点）
- Step 6: 筛选（基于相似度）
- Step 7: 分类
"""

import json
from typing import List, Dict, Any
from concurrent.futures import ThreadPoolExecutor

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

# 全局线程池 - 用于并行处理
_GLOBAL_THREAD_POOL = ThreadPoolExecutor(max_workers=16, thread_name_prefix="FormExtraction")


class ScriptFormExtractionAgent(BaseLLMAgent):
    """形式提取Agent - 从文字到视觉到整体的递进过程（视频版）"""

    def __init__(
        self,
        name: str = "form_extraction_agent",
        description: str = "形式提取Agent",
        model_provider: str = "google_genai",
        temperature: float = 0.1,
        max_tokens: int = 40960
    ):
        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 """你是一个专业的内容表达分析专家，擅长分析短视频的呈现方式和表达手法。

# 核心定义

## 形式（Form）："怎么呈现"
表达方式和呈现手段，必须依附于实质内容。

### 具象概念形式
- **定义**：对具象概念的描述、修饰、表达方式（包括画面文字 + 口播）
- **特征**：文字的描述、修饰、表达手法
- **范围**：语气、强调手法、修辞、叙述方式等文字层面的特征
- **重要**：包括画面文字和口播内容的表达方式

### 具体元素形式
- **定义**：视觉画面的形式，支撑具体元素、具象概念的视觉呈现
- **特征**：图像的视觉表现手法、外观特征
- **范围**：构图、色彩、光影、质感、视觉技巧等图片层面的特征
- **重要**：不关注图片里的文字说了什么，只关注视觉呈现

### 整体形式
- **定义**：整体呈现的形式，不针对局部元素/概念
- **特征**：多模态组合方式、整体结构特征
- **范围**：图文配比、内容组织、呈现策略、节奏控制等整体层面的特征

## 区分方法："剥离测试"
问题：如果去掉所有表达手法/风格/技巧，这个特征还存在吗？
- 存在 → 实质（内容本身）
- 不存在/失去意义 → 形式（表达方式）
"""

    def process(self, state: dict) -> dict:
        """执行完整的形式提取流程（Step 1-7）"""
        logger.info("=== 开始形式元素提取（视频版，完整流程） ===")

        # 从 state 中获取视频文件（视频版本核心输入）
        video_file = get_video_file_from_state(state)
        if not video_file:
            logger.error("无法从 state 中获取视频文件，形式提取终止")
            return {
                "concrete_concept_forms": [],
                "concrete_element_forms": [],
                "overall_forms": [],
                "form_elements": [],
                "form_scored_result": {},
                "form_filtered_ids": [],
                "form_categorized_result": {},
                "form_final_elements": []
            }

        # 处理灵感点：支持列表和字典两种格式
        inspiration_points_raw = state.get("inspiration_points", {})
        if isinstance(inspiration_points_raw, list):
            inspiration_points = inspiration_points_raw
        elif isinstance(inspiration_points_raw, dict):
            # 兼容旧格式：{"points": [...]} 或直接是列表
            inspiration_points = inspiration_points_raw.get("points", [])
        else:
            inspiration_points = []
        
        purpose_points = state.get("purpose_points", [])
        key_points = state.get("key_points", [])

        # 获取最终达标的实质元素（优先使用 substance_final_elements）
        substance_final_elements = state.get("substance_final_elements", [])

        if substance_final_elements:
            concrete_elements = [
                elem
                for elem in substance_final_elements
                if elem.get("维度", {}).get("二级") == "具体元素"
            ]
            concrete_concepts = [
                elem
                for elem in substance_final_elements
                if elem.get("维度", {}).get("二级") == "具象概念"
            ]
            # 抽象概念
            abstract_concepts = [
                elem
                for elem in substance_final_elements
                if elem.get("维度", {}).get("二级") == "抽象概念"
            ]
            # 隐含概念相关逻辑已移除
            implicit_concepts: List[dict] = []
        else:
            concrete_elements = state.get("concrete_elements", [])
            concrete_concepts = state.get("concrete_concepts", [])
            abstract_concepts = state.get("abstract_concepts", [])
            # 隐含概念相关逻辑已移除，这里不再从 state 中读取
            implicit_concepts: List[dict] = []

        # 只保留形式类关键点
        form_key_points = [
            kp for kp in key_points
            if kp.get("维度大类") == "形式"
        ] if key_points else []

        logger.info(
            "输入: 具体元素=%d, 具象概念=%d, 抽象概念=%d, "
            "灵感点=%d, 目的点=%d, 形式关键点=%d",
            len(concrete_elements),
            len(concrete_concepts),
            len(abstract_concepts),
            len(inspiration_points) if isinstance(inspiration_points, list) else 0,
            len(purpose_points) if isinstance(purpose_points, list) else 0,
            len(form_key_points),
        )

        # Step 1: 提取具象概念形式
        logger.info("▶ Step 1: 提取具象概念形式")
        concrete_concept_forms = self._step1_extract_concrete_concept_forms(
            video_file, concrete_concepts
        )
        logger.info(
            f"✅ Step 1 完成 - 提取了 {len(concrete_concept_forms)} 个具象概念形式"
        )
        if concrete_concept_forms:
            logger.info(
                f"   具象概念形式列表: {[f.get('名称', 'N/A') for f in concrete_concept_forms[:5]]}"
                + (f" (共{len(concrete_concept_forms)}个)" if len(concrete_concept_forms) > 5 else "")
            )

        # Step 2: 提取具体元素形式
        logger.info("▶ Step 2: 提取具体元素形式")
        concrete_element_forms = self._step2_extract_concrete_element_forms(
            video_file, concrete_elements, concrete_concepts
        )
        logger.info(
            f"✅ Step 2 完成 - 提取了 {len(concrete_element_forms)} 个具体元素形式"
        )
        if concrete_element_forms:
            logger.info(
                f"   具体元素形式列表: {[f.get('名称', 'N/A') for f in concrete_element_forms[:5]]}"
                + (f" (共{len(concrete_element_forms)}个)" if len(concrete_element_forms) > 5 else "")
            )

        # Step 3: 提取整体形式
        logger.info("▶ Step 3: 提取整体形式")
        overall_forms = self._step3_extract_overall_forms(
            video_file,
            concrete_elements,
            concrete_concepts,
            abstract_concepts,
            implicit_concepts,
        )
        logger.info(
            f"✅ Step 3 完成 - 提取了 {len(overall_forms)} 个整体形式"
        )
        if overall_forms:
            logger.info(
                f"   整体形式列表: {[f.get('名称', 'N/A') for f in overall_forms[:5]]}"
                + (f" (共{len(overall_forms)}个)" if len(overall_forms) > 5 else "")
            )

        # 合并所有形式元素
        all_form_elements = (
            concrete_concept_forms + concrete_element_forms + overall_forms
        )

        logger.info(
            f"📊 Step 1-3 汇总 - 总计提取: {len(all_form_elements)} 个形式元素 "
            f"(具象概念形式:{len(concrete_concept_forms)}, "
            f"具体元素形式:{len(concrete_element_forms)}, "
            f"整体形式:{len(overall_forms)})"
        )

        # Step 4: 共性分析（统计形式在视频中的出现频次）
        logger.info("▶ Step 4: 共性分析")
        analyzed_result = self._step4_commonality_analysis(
            video_file,
            all_form_elements,
        )
        logger.info(
            f"✅ Step 4 完成 - 分析了 {len(analyzed_result)} 个形式元素的频次"
        )
        if analyzed_result:
            # 统计频次分布
            freq_distribution = {}
            for item in analyzed_result:
                freq = item.get("出现频次", 0)
                freq_distribution[freq] = freq_distribution.get(freq, 0) + 1
            logger.info(
                f"   频次分布: {dict(sorted(freq_distribution.items(), reverse=True)[:5])}"
            )
            # 显示高频元素
            high_freq_items = sorted(analyzed_result, key=lambda x: x.get("出现频次", 0), reverse=True)[:3]
            if high_freq_items:
                logger.info(
                    f"   高频元素(前3): {[(item.get('名称', 'N/A'), item.get('出现频次', 0)) for item in high_freq_items]}"
                )

        # Step 5: 多维度评分
        logger.info("▶ Step 5: 多维度评分")
        scored_result = self._step5_multi_dimensional_scoring(
            all_form_elements, inspiration_points, purpose_points, form_key_points
        )
        logger.info(
            f"✅ Step 5 完成 - 多维度评分结果: "
            f"灵感点={len(scored_result.get('灵感点', []))}个元素, "
            f"目的点={len(scored_result.get('目的点', []))}个元素, "
            f"关键点={len(scored_result.get('关键点', []))}个元素"
        )
        # 统计有评分的元素数量
        scored_element_ids = set()
        for dimension in ["灵感点", "目的点", "关键点"]:
            for item in scored_result.get(dimension, []):
                if isinstance(item, dict):
                    scored_element_ids.add(item.get("id"))
        logger.info(
            f"   有评分的元素总数: {len(scored_element_ids)}/{len(all_form_elements)}"
        )

        # Step 6: 筛选
        logger.info("▶ Step 6: 筛选")
        filtered_ids = self._step6_filter_elements(analyzed_result, scored_result)
        logger.info(
            f"✅ Step 6 完成 - 筛选结果: {len(filtered_ids)}/{len(all_form_elements)} 个元素通过筛选"
        )
        if filtered_ids:
            filtered_elements_info = [
                elem.get("名称", "N/A") 
                for elem in all_form_elements 
                if elem.get("id") in filtered_ids[:5]
            ]
            logger.info(
                f"   通过筛选的元素(前5): {filtered_elements_info}"
                + (f" (共{len(filtered_ids)}个)" if len(filtered_ids) > 5 else "")
            )

        # Step 7: 分类
        logger.info("▶ Step 7: 分类")
        categorized_result = self._step7_categorize_elements(
            all_form_elements, filtered_ids
        )
        logger.info(
            f"✅ Step 7 完成 - 分类结果: {len(categorized_result)} 个维度完成分类"
        )
        for dim_name, dim_result in categorized_result.items():
            classifications = dim_result.get("元素分类", [])
            logger.info(
                f"   {dim_name}: {len(classifications)} 个元素完成分类"
            )

        # 合并最终信息
        logger.info("▶ 合并最终信息")
        final_elements = self._merge_all_info(
            all_form_elements, scored_result,
            filtered_ids, categorized_result
        )
        logger.info(
            f"✅ 合并完成 - 最终元素数: {len(final_elements)}"
        )
        if final_elements:
            # 按维度统计最终元素
            final_dimension_stats = {}
            for elem in final_elements:
                dim = elem.get("维度", {}).get("二级", "未知")
                final_dimension_stats[dim] = final_dimension_stats.get(dim, 0) + 1
            logger.info(
                f"   最终元素维度分布: {final_dimension_stats}"
            )

        logger.info(f"🎉 形式元素提取完成 - 最终元素数: {len(final_elements)}")

        return {
            "concrete_concept_forms": concrete_concept_forms,
            "concrete_element_forms": concrete_element_forms,
            "overall_forms": overall_forms,
            "form_elements": all_form_elements,
            "form_analyzed_result": analyzed_result,
            "form_scored_result": scored_result,
            "form_filtered_ids": filtered_ids,
            "form_categorized_result": categorized_result,
            "form_final_elements": final_elements
        }

    # ========== Step 1-3: 形式提取 ==========

    def _step1_extract_concrete_concept_forms(
        self,
        video_file,
        concrete_concepts: List[dict]
    ) -> List[dict]:
        """Step 1: 提取具象概念形式 - 视频中对具象概念的表达方式（画面文字 + 口播）"""
        if not self.is_initialized:
            self.initialize()

        if not video_file:
            logger.warning("⚠️  没有视频文件，跳过具象概念形式提取")
            return []

        if not concrete_concepts:
            logger.warning("⚠️  没有具象概念，跳过具象概念形式提取")
            return []

        concepts_text = json.dumps([
            {"id": c.get("id"), "名称": c.get("名称"), "描述": c.get("描述"), "来源": c.get("来源", []),  # 图片来源
                "出现段落列表": [p.get("段落ID") for p in c.get("共性分析", {}).get("出现段落列表", [])]}
            for c in concrete_concepts
        ], ensure_ascii=False, indent=2)

        prompt = f"""# 任务
从视频中提取"具象概念形式"元素（关注画面中的文字呈现方式和口播表达方式）

# 已提取的具象概念（供参考，不允许从中直接抄写内容）
{concepts_text}

# 核心定义

## 具象概念形式
- 定义：对具象概念的描述、修饰、表达方式（包括画面文字呈现方式 + 口播表达方式）
- 特征：表达方式本身，而不是概念内容
- 包含：
  - 对概念的强调方式（如：反复重复、放大字号、加重语气）
  - 画面文字呈现方式（如：字体、颜色、大小、位置、动画等）
  - 口播语气特性（如：教学口吻、科普式表达、聊天式口吻、语气特征、语速、停顿等）
  - 口播内容的修饰手法（如：夸张、比喻、反问、排比）
  - 口播内容的叙述方式（如：第一人称、第二人称、对话体）
  - 口播内容的节奏特征（如：语速快慢、停顿设计、重音、语气变化等）

## 提取原则
1. 只提取画面文字展示方式和口播表达方式，不涉及视觉呈现
2. 必须依附于已提取的具象概念
3. 使用"剥离测试"：去掉表达手法后，概念本身仍存在
4. **原子化命名**：形式名称必须是最原子的，不包含实质内容
5. **建立关联关系**：通过"支撑"字段建立形式与实质的关联边
6. **段落级别**：形式作用域至少是整个段落，不能仅针对单句

## 作用域约束（重要）
1. **段落级别作用域**（核心约束）：
   - 形式的作用域至少是**整个段落级别**，不能仅针对段落中的某一句话
   - 必须是该段落的整体画面文字呈现方式和口播表达方式，而非局部单句的特征
2. **同段落作用域**：形式必须与其支撑的具象概念在同一段落中
3. **事实对应关系**：形式对概念的支撑必须在视频中有明确的对应关系
   - 不能仅凭推测建立支撑关系，必须实际看到这种支撑

# 输出json结构
[
  {{
    "id": "从1开始的自增序列",
    "名称": "单一名词或短语（必须是原子化的形式名称）",
    "描述": "说明形式的本质是什么，以及在视频中的具体定义",
    "维度": {{"一级": "形式", "二级": "具象概念形式"}},
    "支撑": [{{"id":"具象概念id", "名称":"具象概念名称"}}],
    "推理": "在文字中的具体体现方式（引用视频中的说法或呈现方式）",
    "段落来源": ["段落ID1", "段落ID2"]
  }}
]
"""

        result = LLMInvoker.safe_invoke_video_analysis(
            operation_name="具象概念形式提取",
            video_file=video_file,
            prompt=prompt,
            agent=self,
            fallback=[]
        )

        for idx, form in enumerate(result, 1):
            form["id"] = f"具象概念形式-{idx}"

        logger.info(f"Step 1 完成 - 提取了 {len(result)} 个具象概念形式")
        if result:
            logger.debug(f"   具象概念形式详情: {[{'id': r.get('id'), '名称': r.get('名称')} for r in result[:3]]}")
        return result

    def _step2_extract_concrete_element_forms(
        self,
        video_file,
        concrete_elements: List[dict],
        concrete_concepts: List[dict]
    ) -> List[dict]:
        """Step 2: 提取具体元素形式 - 视觉呈现+映射关系（包括独立描述和映射关系）"""
        if not self.is_initialized:
            self.initialize()

        if not video_file:
            logger.warning("⚠️  没有视频文件，跳过具体元素形式提取")
            return []

        elements_text = json.dumps([
            {"id": e.get("id"), "名称": e.get("名称"), "描述": e.get("描述"),
                "来源": e.get("来源", []),  # 图片来源
                "出现段落列表": [p.get("段落ID") for p in e.get("共性分析", {}).get("出现段落列表", [])]
            }
            for e in concrete_elements
        ], ensure_ascii=False, indent=2) if concrete_elements else "无"

        concepts_text = json.dumps([
            {"id": c.get("id"), "名称": c.get("名称"), "描述": c.get("描述"),
                "来源": c.get("来源", []),  # 图片来源
                "出现段落列表": [p.get("段落ID") for p in c.get("共性分析", {}).get("出现段落列表", [])]
            }
            for c in concrete_concepts
        ], ensure_ascii=False, indent=2) if concrete_concepts else "无"

        prompt = f"""# 任务
从视频中提取"具体元素形式"元素

# 已提取的具体元素
{elements_text}

# 已提取的具象概念
{concepts_text}

# 核心定义

## 具体元素形式
- **定义**：视觉画面的形式，支撑具体元素、具象概念的视觉呈现以及背景音乐、音效等非口播内容的声音呈现方式
- **特征**：图像的视觉表现手法、外观特征或者背景音乐和音效的类别、风格、节奏等
- **范围**：构图、色彩、光影、质感、视觉技巧等图片层面的特征或者声音方面的特征
- **重要**：不关注画面里的文字以及口播内容说了什么，只关注视觉呈现以及声音呈现方式

## 提取原则
1. **视觉性**：必须是画面层面或者背景音乐音效层面的形式，而非文字层面或者口播内容层面
2. **边界明确**：不关注画面里的文字以及口播内容，只关注视觉呈现手法或者声音呈现方式
3. **支撑性**：可以支撑具体元素，也可以支撑具象概念
4. **语义完整性**：必须提取具有完整独立语义的形式特征
5. 使用"剥离测试"：去掉表达方式后，元素本身仍存在
6. **段落级别**：形式作用域至少是整个段落，不能仅针对单个元素

## 作用域约束（重要）
1. **段落级别作用域**（核心约束）：
   - 形式的作用域至少是**整个段落级别**，不能仅针对段落中的某一句话
   - 必须是该段落的整体呈现方式，而非局部单个元素的特征
2. **同段落作用域**：形式必须与其支撑的实质元素/概念在同一段落中
3. **事实对应关系**：形式对实质的支撑必须在视频中有明确的视觉对应关系
   - 不能仅凭推测建立支撑关系，必须在画面中实际看到这种支撑

# 命名与关联分离原则（重要）

## 核心规则
- **名称**：只描述形式手法本身（纯形式词汇）
- **支撑**：说明这个形式手法应用在哪些实质内容上（通过id关联具体元素/具象概念）
- **推理**：说明具体如何体现（在图像中的呈现细节或者声音方面的呈现方式）
- **段落来源**：记录形式来自哪个段落

## 关键要求
1. **名称的纯粹性**：名称中不能包含任何实质内容（具体元素名称、具象概念名称等）
2. **通过支撑建立关联**：实质内容通过"支撑"字段的id关联，而非写在名称中
3. **推理的具体性**：在推理中详细说明这个形式手法如何应用在支撑的实质内容上
4. **来源的准确性**：图片来源和段落来源必须与支撑的实质元素/概念一致

# 输出json结构
[
    {{
      "id": "从1开始的自增序列",
      "名称": "单一名词或短语（必须是原子化的纯形式词汇）",
      "描述": "说明形式的本质及在帖子中的具体定义",
      "维度": {{"一级": "形式", "二级": "具体元素形式"}},
      "支撑": {{
        "具体元素": [{{"id":"元素id", "名称":"元素名称"}}],
        "具象概念": [{{"id":"概念id", "名称":"概念名称"}}]
      }},
      "推理": "在画面中或者声音方面的具体体现（说明这个形式手法如何应用在支撑的实质内容上）",
      "段落来源": ["段落ID1", "段落ID2"]
    }}
]

注意：
1. 支撑中"具体元素"必填，"具象概念"可选
2. 名称必须是纯形式词汇，不能包含任何实质内容
3. 段落来源必须与支撑的实质元素/概念的来源一致
4. **作用域约束**：形式必须是段落级别的整体特征，不能是单个元素的局部视觉特征
5. 只输出验证全部通过的形式元素
"""

        result = LLMInvoker.safe_invoke_video_analysis(
            operation_name="具体元素形式提取",
            video_file=video_file,
            prompt=prompt,
            agent=self,
            fallback=[]
        )

        for idx, form in enumerate(result, 1):
            form["id"] = f"具体元素形式-{idx}"

        logger.info(f"Step 2 完成 - 提取了 {len(result)} 个具体元素形式")
        if result:
            logger.debug(f"   具体元素形式详情: {[{'id': r.get('id'), '名称': r.get('名称')} for r in result[:3]]}")
        return result

    def _step3_extract_overall_forms(
        self,
        video_file,
        concrete_elements: List[dict],
        concrete_concepts: List[dict],
        abstract_concepts: List[dict],
        implicit_concepts: List[dict],
    ) -> List[dict]:
        """Step 3: 提取整体形式 - 多模态组合方式、结构特征"""
        if not self.is_initialized:
            self.initialize()

        if not video_file:
            logger.warning("⚠️  没有视频文件，跳过整体形式提取")
            return []

        elements_text = json.dumps(
            [
                {"id": e.get("id"), "名称": e.get("名称"), "描述": e.get("描述"),
                "来源": e.get("来源", []),
                "出现段落列表": [p.get("段落ID") for p in e.get("共性分析", {}).get("出现段落列表", [])]
            }
                for e in concrete_elements
            ],
            ensure_ascii=False,
            indent=2,
        ) if concrete_elements else "无"

        concepts_text = json.dumps(
            [
                {"id": c.get("id"), "名称": c.get("名称"), "描述": c.get("描述"),
                "来源": c.get("来源", []),
                "出现段落列表": [p.get("段落ID") for p in c.get("共性分析", {}).get("出现段落列表", [])]
            }
                for c in concrete_concepts
            ],
            ensure_ascii=False,
            indent=2,
        ) if concrete_concepts else "无"

        abstracts_text = json.dumps(
            [
                {"id": a.get("id"), "名称": a.get("名称"), "描述": a.get("描述"),
                "来源": a.get("来源", []),
                "出现段落列表": [p.get("段落ID") for p in a.get("共性分析", {}).get("出现段落列表", [])]
            }
                for a in abstract_concepts
            ],
            ensure_ascii=False,
            indent=2,
        ) if abstract_concepts else "无"

        prompt = f"""# 任务
从视频中提取"整体形式"元素

# 已提取的具体元素
{elements_text}

# 已提取的具象概念
{concepts_text}

# 已提取的抽象概念
{abstracts_text}

# 核心定义

## 整体形式
- 定义：多模态内容的组合方式、结构特征、整体呈现策略
- 特点：不针对单个元素/概念，而是整体组织方式

## 提取原则
1. 整体性：必须是整体层面的特征，不是局部细节
2. 形式性：使用"剥离测试" - 去掉表达方式后内容失去结构
3. 可命名性：能用简洁的名词/短语概括
4. 避免重复：与具体元素形式、具象概念形式区分开
7. **宏观层面**：作用范围至少是整个段落或多段落，不能是单句或局部内容

## 作用域约束（重要）
1. **段落级别作用域**（核心约束）：
   - 形式的作用域至少是**整个段落级别**，不能仅针对段落中的局部内容
   - 整体形式通常作用于整个帖子或多个段落，必须是宏观层面的特征
2. **支撑关系**：整体形式支撑特定实质元素时，必须记录这些元素所在的段落
3. **来源标注**：标注整体形式主要体现在哪些段落中

## 常见维度参考（不限于此）
- 多模态组合：画面+口播+字幕的配比、关系、时序、模态切换
- 结构方式：内容组织逻辑（并列/递进/对比/因果/总分/时间/空间顺序等）
- 呈现策略：整体表达风格（教程式/故事式/问答式/列表式/对话式等）
- 节奏控制：信息密度、镜头节奏、高潮设计
- 视角设计：叙述视角（第一人称/第二人称/第三人称）
- 互动方式：对观众的提问、召回、引导

# 输出json结构
[
  {{
    "id": "从1开始的自增序列",
    "名称": "单一名词或短语（简洁准确）",
    "描述": "说明形式的本质是什么，以及在视频中的具体定义",
    "维度": {{"一级": "形式", "二级": "整体形式"}},
    "支撑": {{
      "具体元素": [{{"id":"元素id1", "名称":"元素名称1"}}, {{"id":"元素id2", "名称":"元素名称2"}}],
      "具象概念": [{{"id":"概念id1", "名称":"概念名称1"}}, {{"id":"概念id2", "名称":"概念名称2"}}]
    }},
    "推理": "在视频中的具体体现（结合镜头、节奏、结构说明）",
    "段落来源": ["段落ID1", "段落ID2"]
  }}
]
"""

        result = LLMInvoker.safe_invoke_video_analysis(
            operation_name="整体形式提取",
            video_file=video_file,
            prompt=prompt,
            agent=self,
            fallback=[]
        )

        for idx, form in enumerate(result, 1):
            form["id"] = f"整体形式-{idx}"

        logger.info(f"Step 3 完成 - 提取了 {len(result)} 个整体形式")
        if result:
            logger.debug(f"   整体形式详情: {[{'id': r.get('id'), '名称': r.get('名称')} for r in result[:3]]}")
        return result

    # ========== Step 4-7: 后续处理 ==========

    def _step4_commonality_analysis(
        self,
        video_file,
        form_elements: List[dict],
    ) -> List[dict]:
        """Step 4: 共性分析 - 基于视频统计形式在全文中的出现频次"""
        if not form_elements:
            return []

        if not video_file:
            logger.warning("⚠️  没有视频文件，跳过形式共性分析")
            return []

        if not self.is_initialized:
            self.initialize()

        elements_text = self._build_simple_items_text(form_elements)

        prompt = f"""# 任务
基于短视频内容，对已提取的形式元素进行"共性分析"，统计每个形式元素在整个视频中的出现频次。

# 已提取的形式元素列表
{elements_text}

# 统计规则

## 1. 具象概念形式（文字表达方式）
- **出现频次**: 统计该表达方式（如"反复重复强调"、"排比句式"、"教学口吻"等）
  在视频中出现的总次数（包括口播和画面文字）。

## 2. 具体元素形式（视觉呈现方式）
- **出现频次**: 统计该视觉形式（如"顶视拍摄"、"暖色调光影"、"拼接画面"等）
  在视频画面中被使用的总次数。

## 3. 整体形式（整体结构 / 节奏 / 呈现策略）
- **出现频次**: 统计该整体特征在视频中被体现的次数；
  通常整体结构类形式频次为 1，但如果在不同段落中多次明显重复，也可以计为 >1。

# 输出（JSON）
[
  {{
    "id": "元素id",
    "名称": "元素名称",
    "出现频次": 0
  }}
]
"""

        analyzed_items = LLMInvoker.safe_invoke_video_analysis(
            operation_name="形式共性分析",
            video_file=video_file,
            prompt=prompt,
            agent=self,
            fallback=[]
        )

        logger.info(f"共性分析完成 - 统计了 {len(analyzed_items)} 个形式元素的频次")
        return analyzed_items

    def _step5_multi_dimensional_scoring(
        self,
        form_elements: List[dict],
        inspiration_points: list,
        purpose_points: list,
        form_key_points: list
    ) -> dict:
        """Step 5: 多维度评分 - 评估与灵感点/目的点/形式关键点的相似度"""
        logger.info(f"【多维度评分】输入: {len(form_elements)} 个形式元素")
        logger.info(
            f"   评分目标: 灵感点={len(inspiration_points) if isinstance(inspiration_points, list) else 0}个, "
            f"目的点={len(purpose_points) if isinstance(purpose_points, list) else 0}个, "
            f"关键点={len(form_key_points) if isinstance(form_key_points, list) else 0}个"
        )

        futures = {}

        if form_elements and inspiration_points and isinstance(inspiration_points, list) and len(inspiration_points) > 0:
            futures["灵感点"] = _GLOBAL_THREAD_POOL.submit(
                self._evaluate_support_in_batches,
                form_elements, inspiration_points, "灵感点"
            )
        else:
            logger.info("   ⏭️  跳过灵感点评分 (原因: 灵感点列表为空)")

        if form_elements and purpose_points and isinstance(purpose_points, list) and len(purpose_points) > 0:
            futures["目的点"] = _GLOBAL_THREAD_POOL.submit(
                self._evaluate_support_in_batches,
                form_elements, purpose_points, "目的点"
            )
        else:
            logger.info("   ⏭️  跳过目的点评分 (原因: 目的点列表为空)")

        if form_elements and form_key_points and isinstance(form_key_points, list) and len(form_key_points) > 0:
            futures["关键点"] = _GLOBAL_THREAD_POOL.submit(
                self._evaluate_support_in_batches,
                form_elements, form_key_points, "关键点"
            )
        else:
            logger.info("   ⏭️  跳过关键点评分 (原因: 关键点列表为空)")

        result = {
            "灵感点": [],
            "目的点": [],
            "关键点": []
        }

        for dimension_name, future in futures.items():
            try:
                result[dimension_name] = future.result()
            except Exception as e:
                logger.error(f"{dimension_name} 评估失败: {e}")

        logger.info("多维度评分完成")
        # 详细统计每个维度的评分情况
        for dimension_name in ["灵感点", "目的点", "关键点"]:
            dimension_data = result.get(dimension_name, [])
            if dimension_data:
                elements_with_scores = sum(
                    1 for item in dimension_data 
                    if item.get("相似度结果")
                )
                logger.info(
                    f"   {dimension_name}: {elements_with_scores}/{len(dimension_data)} 个元素有相似度评分"
                )
        return result

    def _evaluate_support_in_batches(
        self,
        elements: list,
        points: list,
        point_type: str,
        max_batch_product: int = 100
    ) -> list:
        """分批评估相似度"""
        if not self.is_initialized:
            self.initialize()

        if not points:
            return []

        num_elements = len(elements)
        num_points = len(points)
        max_elements_per_batch = max(1, int(max_batch_product / num_points))
        num_batches = (num_elements + max_elements_per_batch - 1) // max_elements_per_batch

        logger.info(f"{point_type}分批评估: {num_batches} 批")

        batch_futures = {}
        for batch_idx in range(num_batches):
            start_idx = batch_idx * max_elements_per_batch
            end_idx = min(start_idx + max_elements_per_batch, num_elements)
            batch_elements = elements[start_idx:end_idx]

            future = _GLOBAL_THREAD_POOL.submit(
                self._evaluate_support_single_batch,
                batch_elements, points, point_type
            )
            batch_futures[batch_idx] = future

        all_results = []
        for batch_idx, future in batch_futures.items():
            try:
                batch_result = future.result()
                if batch_result:
                    all_results.extend(batch_result)
            except Exception as e:
                logger.error(f"批次 {batch_idx + 1} 失败: {e}")

        merged_results = self._merge_batch_results(all_results)
        return merged_results

    def _evaluate_support_single_batch(
        self,
        batch_elements: list,
        points: list,
        point_type: str
    ) -> list:
        """单批次评估"""
        if not self.is_initialized:
            self.initialize()

        elements_text = self._build_simple_items_text(batch_elements)
        points_text = self._build_points_text(point_type, points)

        prompt = f"""# 元素列表
{elements_text}

# 点列表
{points_text}

# 任务
对每个元素计算元素与点的文本相似度和语义相似度

# 输出（JSON）
[
  {{
    "id": "元素id",
    "名称": "元素名称",
    "相似度结果": [
      {{
        "点": "点的名称",
        "语义相似度": 0.21,
        "语义相似度理由": "理由",
        "文本相似度": 0.33,
        "文本相似度理由": "理由"
      }}
    ]
  }}
]
"""

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

        result = LLMInvoker.safe_invoke(
            self,
            f"评估支撑{point_type}",
            messages,
            fallback=[]
        )

        return result

    def _merge_batch_results(self, all_results: list) -> list:
        """合并批次结果，每个元素只保留最相关的1-2个点"""
        if not all_results:
            return []

        merged_map = {}
        for item in all_results:
            element_id = item.get("id")
            if element_id not in merged_map:
                merged_map[element_id] = {
                    "id": element_id,
                    "名称": item.get("名称"),
                    "相似度结果": []
                }
            if not merged_map[element_id]["相似度结果"]:
                merged_map[element_id]["相似度结果"] = item.get("相似度结果", [])

        for element_data in merged_map.values():
            similarity_results = element_data.get("相似度结果", [])
            if not similarity_results:
                continue

            max_text_sim_point = max(similarity_results, key=lambda x: x.get("文本相似度", 0))
            max_semantic_sim_point = max(similarity_results, key=lambda x: x.get("语义相似度", 0))

            if max_text_sim_point.get("点") == max_semantic_sim_point.get("点"):
                filtered_results = [max_text_sim_point]
            else:
                filtered_results = [max_text_sim_point, max_semantic_sim_point]

            element_data["相似度结果"] = filtered_results

        return list(merged_map.values())

    def _step6_filter_elements(
        self,
        analyzed_result: list,
        scored_result: dict
    ) -> list:
        """Step 6: 筛选形式元素

        保留条件（必须全部满足）：
        - 共性分析中出现频次 > 1
        - 且满足以下任一相似度条件：
          - 灵感点语义相似度 > 0.7 或 文本相似度 > 0.8
          - 目的点语义相似度 > 0.7 或 文本相似度 > 0.8
          - 关键点(形式)语义相似度 > 0.7 或 文本相似度 > 0.8
        """
        logger.info("【筛选】输入: 形式元素共性分析结果 + 评分结果")

        # 共性分析映射（频次）
        analyzed_map: Dict[str, dict] = {}
        for item in analyzed_result or []:
            if not isinstance(item, dict):
                continue
            element_id = item.get("id")
            if element_id:
                analyzed_map[element_id] = item

        # 评分结果映射（相似度）
        scored_map: Dict[str, Dict[str, list]] = {}
        for dimension in ["灵感点", "目的点", "关键点"]:
            dimension_data = scored_result.get(dimension, [])
            for item in dimension_data:
                if not isinstance(item, dict):
                    continue
                element_id = item.get("id")
                if element_id not in scored_map:
                    scored_map[element_id] = {}
                scored_map[element_id][dimension] = item.get("相似度结果", [])

        filtered_ids: List[str] = []
        rejected_ids: List[str] = []

        element_name_map: Dict[str, str] = {}
        for dimension in ["灵感点", "目的点", "关键点"]:
            for item in scored_result.get(dimension, []):
                if isinstance(item, dict):
                    element_id = item.get("id")
                    element_name = item.get("名称", "N/A")
                    if element_id and element_id not in element_name_map:
                        element_name_map[element_id] = element_name

        for element_id, scored_data in scored_map.items():
            element_name = element_name_map.get(element_id, "N/A")
            analysis_data = analyzed_map.get(element_id, {})
            frequency = analysis_data.get("出现频次", 0)

            # 频次过滤：出现频次<=1 的形式直接过滤
            if frequency < 1:
                rejected_ids.append(element_id)
                logger.info(
                    "❌ 过滤: id=%s, name=%s, 原因=出现频次<1 (frequency=%d)",
                    element_id,
                    element_name,
                    frequency,
                )
                continue

            should_keep = False
            reasons: List[str] = []

            for dimension_name, thresholds in [
                ("灵感点", {"语义相似度": 0.7, "文本相似度": 0.8}),
                ("目的点", {"语义相似度": 0.7, "文本相似度": 0.8}),
                ("关键点", {"语义相似度": 0.7, "文本相似度": 0.8})
            ]:
                similarity_results = scored_data.get(dimension_name, [])
                for sim_item in similarity_results:
                    if sim_item.get("语义相似度", 0) > thresholds["语义相似度"]:
                        should_keep = True
                        reasons.append(f"{dimension_name}语义>{thresholds['语义相似度']}")
                    if sim_item.get("文本相似度", 0) > thresholds["文本相似度"]:
                        should_keep = True
                        reasons.append(f"{dimension_name}文本>{thresholds['文本相似度']}")

            if should_keep:
                filtered_ids.append(element_id)
                logger.info(
                    f"✅ 保留: id={element_id}, name={element_name}, "
                    f"frequency={frequency}, 原因={reasons[0] if reasons else '频次+相似度达标'}"
                )
            else:
                rejected_ids.append(element_id)
                logger.info(
                    "❌ 过滤: id=%s, name=%s, 原因=频次>2但未通过相似度筛选 (frequency=%d)",
                    element_id,
                    element_name,
                    frequency,
                )

        logger.info(f"筛选完成: {len(filtered_ids)}/{len(scored_map)} 通过")
        # 统计筛选原因
        if filtered_ids:
            logger.info(
                f"   通过筛选的元素ID(前10): {filtered_ids[:10]}"
                + (f" (共{len(filtered_ids)}个)" if len(filtered_ids) > 10 else "")
            )
        if rejected_ids:
            logger.info(
                f"   被过滤的元素数量: {len(rejected_ids)}"
            )
        return filtered_ids

    def _step7_categorize_elements(
        self,
        form_elements: List[dict],
        filtered_ids: list
    ) -> dict:
        """Step 7: 元素分类 - 按二级维度分别分类"""
        logger.info(f"【分类】输入: {len(filtered_ids)} 个筛选后的形式元素")

        if not filtered_ids:
            return {}

        filtered_elements = [
            elem for elem in form_elements
            if elem.get("id") in filtered_ids
        ]

        dimension_groups: Dict[str, List[dict]] = {
            "具象概念形式": [],
            "具体元素形式": [],
            "整体形式": []
        }

        for elem in filtered_elements:
            second_level = elem.get("维度", {}).get("二级", "")
            if second_level in dimension_groups:
                dimension_groups[second_level].append(elem)

        categorization_results: Dict[str, Any] = {}
        futures = {}

        for dimension_name, elements in dimension_groups.items():
            if not elements:
                continue

            future = _GLOBAL_THREAD_POOL.submit(
                self._categorize_single_dimension,
                dimension_name,
                elements
            )
            futures[dimension_name] = future

        for dimension_name, future in futures.items():
            try:
                categorization_results[dimension_name] = future.result()
            except Exception as e:
                logger.error(f"{dimension_name} 分类失败: {e}")
                categorization_results[dimension_name] = {"元素分类": []}

        logger.info(f"分类完成: {len(categorization_results)} 个维度")
        # 统计每个维度的分类情况
        for dim_name, dim_result in categorization_results.items():
            classifications = dim_result.get("元素分类", [])
            classified_count = sum(1 for c in classifications if c.get("分类"))
            logger.info(
                f"   {dim_name}: {classified_count}/{len(classifications)} 个元素有分类"
            )
        return categorization_results

    def _categorize_single_dimension(
        self,
        dimension_name: str,
        elements: list
    ) -> dict:
        """对单个维度的元素进行分类"""
        if not self.is_initialized:
            self.initialize()

        elements_text = json.dumps([
            {"id": elem.get("id"), "名称": elem.get("名称"), "描述": elem.get("描述")}
            for elem in elements
        ], ensure_ascii=False, indent=2)

        prompt = f"""# 任务
对"{dimension_name}"维度的元素进行分类

# 元素列表
{elements_text}

# 分类要求

## 核心原则
1. 单一原子名词: 分类名称必须是单一的原子名词
2. MECE原则: 分类之间相互独立、完全穷尽
3. 确定性归属: 每个元素只能归属唯一一个分类
4. 层级限制: 最多2层
5. 元素可以没有分类，不要强行归类
6. 分类下面至少要有2个元素，否则不要分类

## 形式维度的分类逻辑
- 核心原则：按照表现手法、呈现方式、修辞技巧等角度来分类

# 输出格式（JSON）
{{
  "元素分类": [
    {{
      "元素id": "元素的ID",
      "元素名称": "元素名称",
      "分类": ["一级分类","二级分类","..."]
    }}
  ]
}}
"""

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

        result = LLMInvoker.safe_invoke(
            self,
            f"分类-{dimension_name}",
            messages,
            fallback={}
        )

        return result

    def _merge_all_info(
        self,
        form_elements: List[dict],
        scored_result: dict,
        filtered_ids: list,
        categorized_result: dict
    ) -> list:
        """合并所有信息 - 每个元素包含所有中间信息"""
        logger.info(f"【合并信息】输入: {len(filtered_ids)} 个筛选后的元素ID")

        if not filtered_ids:
            return []

        extraction_map = {item.get("id"): item for item in form_elements}

        scored_map: Dict[str, Dict[str, list]] = {}
        for dimension in ["灵感点", "目的点", "关键点"]:
            dimension_data = scored_result.get(dimension, [])
            for item in dimension_data:
                if not isinstance(item, dict):
                    continue
                element_id = item.get("id")
                if element_id not in scored_map:
                    scored_map[element_id] = {}
                similarity_results = item.get("相似度结果", [])
                sorted_results = sorted(
                    similarity_results,
                    key=lambda x: (x.get("文本相似度", 0), x.get("语义相似度", 0)),
                    reverse=True
                )
                scored_map[element_id][dimension] = sorted_results

        category_map: Dict[str, Any] = {}
        for dimension_data in categorized_result.values():
            element_classifications = dimension_data.get("元素分类", [])
            for classification in element_classifications:
                element_id = classification.get("元素id")
                category_info = classification.get("分类", {})
                if element_id:
                    category_map[element_id] = category_info

        final_elements = []
        for element_id in filtered_ids:
            base_info = extraction_map.get(element_id, {})
            scoring_info = scored_map.get(element_id, {})
            category_info = category_map.get(element_id, {})

            merged_element = {
                "id": base_info.get("id"),
                "名称": base_info.get("名称"),
                "描述": base_info.get("描述"),
                "维度": base_info.get("维度", {}),
                "分类": category_info,
                "共性分析": None,
                "多维度评分": {
                    "灵感点": scoring_info.get("灵感点", []),
                    "目的点": scoring_info.get("目的点", []),
                    "关键点": scoring_info.get("关键点", [])
                }
            }

            second_level = base_info.get("维度", {}).get("二级", "")
            if second_level == "具象概念形式":
                merged_element["支撑"] = base_info.get("依附于", [])
                merged_element["推理"] = base_info.get("推理", "")
            elif second_level == "具体元素形式":
                merged_element["类型"] = base_info.get("类型", "")
                if merged_element["类型"] == "独立描述":
                    merged_element["支撑"] = base_info.get("依附于", [])
                    merged_element["推理"] = base_info.get("推理", "")
                elif merged_element["类型"] == "映射关系":
                    merged_element["支撑"] = base_info.get("映射关系", {})
            elif second_level == "整体形式":
                merged_element["推理"] = base_info.get("推理", "")
                merged_element["支撑"] = base_info.get("支撑", "")

            final_elements.append(merged_element)

        logger.info(f"合并完成: {len(final_elements)} 个元素")
        # 统计合并后的元素信息
        if final_elements:
            elements_with_scores = sum(
                1 for elem in final_elements
                if any(elem.get("多维度评分", {}).get(dim, []) for dim in ["灵感点", "目的点", "关键点"])
            )
            elements_with_category = sum(
                1 for elem in final_elements
                if elem.get("分类")
            )
            logger.info(
                f"   有评分的元素: {elements_with_scores}/{len(final_elements)}, "
                f"有分类的元素: {elements_with_category}/{len(final_elements)}"
            )
        return final_elements

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

    def _build_simple_items_text(self, elements: list) -> str:
        """构建元素列表文本"""
        grouped_elements: Dict[str, List[Dict[str, Any]]] = {
            "具象概念形式": [],
            "具体元素形式": [],
            "整体形式": []
        }

        for elem in elements:
            element_type = elem.get("维度", {}).get("二级", "N/A")
            element_data = {
                "id": elem.get("id", "N/A"),
                "名称": elem.get("名称", "N/A"),
                "描述": elem.get("描述", "N/A")
            }
            if element_type in grouped_elements:
                grouped_elements[element_type].append(element_data)

        filtered_groups = {k: v for k, v in grouped_elements.items() if v}
        return json.dumps(filtered_groups, ensure_ascii=False, indent=2)

    def _build_points_text(self, point_type: str, points_data) -> str:
        """构建点列表文本"""
        if not points_data:
            return f"无{point_type}信息"
        filtered_points = [
            {"名称": item.get(point_type, "N/A")}
            for item in points_data if isinstance(item, dict)
        ]
        return json.dumps(filtered_points, ensure_ascii=False, indent=2)

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

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


