"""Action Recognition 特化出题策略。

靶向 Agent 在动作识别类题目上的 6 种典型失败机制，通过加权随机
SubPattern 选择为 VLM 出题提供聚焦指令。

与 BaseTaskTypeStrategy 完全自包含，不依赖 QuestionFamilySpec。
采样从 L3 提升至 L2（跨段推理需要更广视野），3 个 SubPattern
进一步覆盖至 L1 以测试全局追踪能力。
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any

from app.question_gen.families import SamplingConstraint
from app.question_gen.strategy import SubPattern

if TYPE_CHECKING:
    import random


# ---------------------------------------------------------------------------
# 6 个 SubPattern 定义
# ---------------------------------------------------------------------------

_PREMATURE_EVIDENCE_ANCHORING = SubPattern(
    name="premature_evidence_anchoring",
    weight=0.20,
    sampling_level_override=1,
    constraint_override=None,
    instruction=(
        "设计一道需要考察视频中完整证据链的动作识别题。"
        "正确答案的关键证据出现在视频后半段或跨多个片段，"
        "但视频前段包含一个看似合理的局部匹配——"
        "Agent 若仅凭首条匹配停止搜索就会出错。"
    ),
    positive_examples=[
        {
            "question": "视频中厨师最终采用了哪种烹饪方式？",
            "answer": "B. 蒸",
            "why": "厨师先演示了炒（前段），但最终菜品使用蒸制（后段），"
            "锚定首段证据的 Agent 会错选'炒'。",
        },
    ],
    negative_examples=[
        {
            "question": "视频开头厨师在做什么？",
            "why": "答案仅需前段信息，不会触发过早锚定失败。",
        },
    ],
    distractor_rules=(
        "将视频前段真实出现的局部匹配动作设为强干扰项——它真实发生，"
        "仅在【时点】这一单一维度上与正解不同（前段 vs 最终结论段）。"
        "其余干扰项亦须是视频中真实发生的动作，严禁缺席事件。"
    ),
)

_TEMPORAL_REASONING_FAILURE = SubPattern(
    name="temporal_reasoning_failure",
    weight=0.20,
    sampling_level_override=1,
    constraint_override=None,
    instruction=(
        "设计一道要求正确排序或定位第 N 次出现的动作识别题。"
        "题目需要 Agent 追踪事件的时间先后顺序，"
        "或准确识别某动作在视频中第几次出现。"
        "打乱时序或错误计数即会答错。"
    ),
    positive_examples=[
        {
            "question": "运动员第三次尝试起跳前做了什么准备动作？",
            "answer": "C. 深蹲热身",
            "why": "需要准确定位'第三次'起跳而非其他次，"
            "时序推理失败的 Agent 会混淆不同次尝试的准备动作。",
        },
    ],
    negative_examples=[
        {
            "question": "运动员在视频中做了什么？",
            "why": "不涉及时序排序或计数，Agent 无需追踪顺序。",
        },
    ],
    distractor_rules=(
        "干扰项必须是视频中真实发生的事件，仅在【事件时序/顺序】这一单一维度上与正解不同——"
        "即同一组真实事件的错误排列或错误的第 N 次定位。"
        "严禁使用视频中未出现的缺席事件作为干扰项。"
    ),
    supports_flip=True,
    flip_axis="before/after",
)

_SEMANTIC_RIGIDITY = SubPattern(
    name="semantic_rigidity",
    weight=0.15,
    sampling_level_override=None,
    constraint_override=None,
    instruction=(
        "设计一道动作识别题，正确选项使用与视频原始描述不同的同义表达。"
        "Agent 需理解语义等价而非依赖字面匹配——"
        "例如视频字幕说'奔跑'，正确选项写作'快速移动'。"
    ),
    positive_examples=[
        {
            "question": "工人对墙面进行了什么操作？",
            "answer": "A. 涂覆保护层",
            "why": "视频中字幕描述为'刷漆'，正确答案改写为'涂覆保护层'，"
            "依赖字面匹配的 Agent 会因找不到完全一致的表述而错选。",
        },
    ],
    negative_examples=[
        {
            "question": "工人在刷漆吗？",
            "why": "选项直接复用视频原文，不考察语义理解。",
        },
    ],
    distractor_rules=(
        "干扰项须基于视频真实内容，仅在【表述/语义】这一单一维度上做文章："
        "保留一个复用视频原始字幕字面、但在题干限定下语义为假的选项作为陷阱，"
        "其余选项描述真实动作的不同同义表述。严禁凭空编造缺席动作。"
    ),
)

_FINE_GRAINED_VISUAL_ACTION = SubPattern(
    name="fine_grained_visual_action",
    weight=0.15,
    sampling_level_override=None,
    constraint_override=None,
    instruction=(
        "设计一道需要区分细粒度动作方式的识别题。"
        "题目聚焦于 HOW（怎么做）而非 WHAT（做什么），"
        "如区分'搅拌'与'翻炒'、'拧'与'拉'等视觉上相似但方式不同的动作。"
    ),
    positive_examples=[
        {
            "question": "维修人员是如何拆卸螺丝的？",
            "answer": "D. 用扳手逆时针旋转",
            "why": "需要区分拆卸的具体方式（扳手 vs 螺丝刀、顺时针 vs 逆时针），"
            "粗粒度识别只能判断'在拆螺丝'，无法区分方式。",
        },
    ],
    negative_examples=[
        {
            "question": "维修人员在做什么？",
            "why": "只需粗粒度动作识别（'拆螺丝'），不考察具体方式。",
        },
    ],
    distractor_rules=(
        "四个选项须是【同一大类动作】的不同执行方式，全部为视频中真实可见的做法，"
        "仅在【执行方式】这一单一维度上不同（如顺/逆时针、扳手/螺丝刀）。"
        "严禁使用明显不相关或视频中未出现的动作作为干扰项。"
    ),
)

_CROSS_SEGMENT_ENTITY_TRACKING = SubPattern(
    name="cross_segment_entity_tracking",
    weight=0.15,
    sampling_level_override=1,
    constraint_override=None,
    instruction=(
        "设计一道需要跨视频段落追踪同一实体动作的识别题。"
        "目标实体在不同片段中外观、称呼或上下文发生变化，"
        "Agent 需要将多段信息合并才能正确回答关于该实体的动作问题。"
    ),
    positive_examples=[
        {
            "question": "穿红色外套的人在视频中总共完成了哪些动作？",
            "answer": "B. 先讲解、后示范、最后总结",
            "why": "该人物在前段穿外套讲解，中段脱外套示范，后段重新穿上总结，"
            "无法跨段追踪的 Agent 会遗漏某段动作。",
        },
        {
            "question": "主持人在节目不同环节中分别做了什么？",
            "answer": "A. 开场介绍、采访嘉宾、总结点评",
            "why": "主持人在不同场景切换中持续出现，需要跨段聚合。",
        },
    ],
    negative_examples=[
        {
            "question": "视频第一个片段中的人在做什么？",
            "why": "仅需单段信息，不考察跨段追踪。",
        },
    ],
    distractor_rules=(
        "干扰项须是视频中【另一真实实体】在相应片段真实做过的动作，"
        "仅在【动作主体】这一单一维度上与正解不同。"
        "严禁编造任何实体未做过的缺席动作。"
    ),
    supports_flip=True,
    flip_axis="first/last",
)

_EVIDENCE_GAP_CONFABULATION = SubPattern(
    name="evidence_gap_confabulation",
    weight=0.15,
    sampling_level_override=None,
    constraint_override=None,
    instruction=(
        "设计一道动作识别题，视频中存在证据空缺（如遮挡、跳切、画外音）。"
        "正确答案承认信息不足或基于间接证据推断，"
        "而非凭空编造因果链。Agent 若虚构缺失证据即会出错。"
    ),
    positive_examples=[
        {
            "question": "画面切走后，演讲者下一步做了什么？",
            "answer": "C. 无法从视频中直接确定",
            "why": "画面跳切导致该动作无直接视觉证据，"
            "倾向于虚构的 Agent 会编造一个看似合理的动作。",
        },
    ],
    negative_examples=[
        {
            "question": "画面中演讲者正在做什么？",
            "why": "动作在画面中可见，不存在证据空缺。",
        },
    ],
    distractor_rules=(
        "正解仅陈述视频中可观测的事实或诚实承认证据不足；"
        "干扰项在【因果完整性】这一单一维度上越界——补上一段视频未展示的因果链，"
        "但其前提元素仍取自视频真实内容（诱导 Agent 顺势编造），而非完全凭空的缺席事件。"
    ),
)

AR_SUB_PATTERNS: tuple[SubPattern, ...] = (
    _PREMATURE_EVIDENCE_ANCHORING,
    _TEMPORAL_REASONING_FAILURE,
    _SEMANTIC_RIGIDITY,
    _FINE_GRAINED_VISUAL_ACTION,
    _CROSS_SEGMENT_ENTITY_TRACKING,
    _EVIDENCE_GAP_CONFABULATION,
)

# 预计算：名称列表 + 权重列表（避免每次 select 重新构建）
_AR_PATTERN_NAMES: list[str] = [sp.name for sp in AR_SUB_PATTERNS]
_AR_PATTERN_WEIGHTS: list[float] = [sp.weight for sp in AR_SUB_PATTERNS]
_AR_PATTERN_BY_NAME: dict[str, SubPattern] = {sp.name: sp for sp in AR_SUB_PATTERNS}


# ---------------------------------------------------------------------------
# ActionRecognitionStrategy
# ---------------------------------------------------------------------------

_SAMPLING_CONSTRAINT = SamplingConstraint(
    min_subtitles=3,
    min_l3_nodes=5,
    require_frames=True,
    cross_l2_span=True,
)


class ActionRecognitionStrategy:
    """Action Recognition 特化出题策略。

    自包含实现，不依赖 BaseTaskTypeStrategy 或 QuestionFamilySpec。
    靶向 6 种典型失败机制，通过加权随机 SubPattern 引导 VLM 出题。

    属性:
        task_type: 题型名 — "Action Recognition"。
        strategy_name: 策略标识 — "ACTION_RECOGNITION"。
        skill_target: 目标失败机制 — "M1_AR"。
        sampling_level: 采样层级 — 2（L2，从 L3 升级以获得跨段视野）。
        sampling_constraint: 采样约束（min_subtitles=3, min_l3_nodes=5, require_frames, cross_l2_span）。
        prompt_template: 出题模板 — "action_recognition.md"。
        leak_probe_template: 泄漏探测模板 — "gate_leak_retrieval.md"。
    """

    @property
    def task_type(self) -> str:
        """返回题型名。"""
        return "Action Recognition"

    @property
    def strategy_name(self) -> str:
        """返回策略标识名。"""
        return "ACTION_RECOGNITION"

    @property
    def skill_target(self) -> str:
        """返回目标失败机制编号。"""
        return "M1_AR"

    @property
    def sampling_level(self) -> int:
        """返回采样层级（L2）。"""
        return 2

    @property
    def sampling_constraint(self) -> SamplingConstraint:
        """返回采样约束。"""
        return _SAMPLING_CONSTRAINT

    @property
    def prompt_template(self) -> str:
        """返回出题 prompt 模板文件名。"""
        return "action_recognition.md"

    @property
    def leak_probe_template(self) -> str:
        """返回泄漏探测模板文件名。"""
        return "gate_leak_retrieval.md"

    @property
    def uses_grounded_selector(self) -> bool:
        """AR 启用候选池 + VLM 视觉打分 selector。"""
        return True

    def select_sub_pattern(self, rng: random.Random) -> SubPattern:
        """按权重随机选择一个 SubPattern。

        参数:
            rng: 随机数生成器（确保可复现）。

        返回:
            选中的 SubPattern 实例（永不为 None）。
        """
        chosen_name = rng.choices(
            _AR_PATTERN_NAMES,
            weights=_AR_PATTERN_WEIGHTS,
            k=1,
        )[0]
        return _AR_PATTERN_BY_NAME[chosen_name]

    def build_prompt_context(self, material: Any, sub_pattern: SubPattern | None) -> dict:
        """构建 prompt 上下文字典。

        参数:
            material: 采样素材（当前未使用，留给管线扩展）。
            sub_pattern: 选中的子模式。

        返回:
            包含 family_name, prompt_template, sub_pattern 的字典。
        """
        return {
            "family_name": self.strategy_name,
            "prompt_template": self.prompt_template,
            "sub_pattern": sub_pattern.name if sub_pattern is not None else None,
        }

    def extra_gates(self, candidate: Any) -> list:
        """返回额外门控列表（当前为空）。

        参数:
            candidate: 候选题目。

        返回:
            空列表。
        """
        return []
