"""跨模块共享类型。"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from pathlib import Path as _Path


@dataclass(frozen=True)
class LLMResponse:
    """LLM/VLM 调用的统一返回值。

    由 adapters 层生成，core 层消费。frozen=True 确保响应不可变。
    """

    content: str
    thinking: str
    model: str
    provider: str
    prompt_tokens: int
    completion_tokens: int
    latency_ms: int
    ttft_ms: float | None
    max_inter_token_ms: float | None
    cache_hit: bool
    call_id: str


@dataclass(frozen=True)
class GeneratedQuestion:
    """单条生成/加载的题目。

    跨层共享类型，被 core/evolution/ 和 app/harness/、app/question_gen/ 使用。
    frozen=True 确保题目不可变。

    属性:
        question_id: 题目唯一标识。
        video_id: 所属视频标识。
        task_type: 题型（如 "Action Reasoning"）。
        question: 题目文本。
        options: 选项元组（如 ("A. ...", "B. ...", "C. ...", "D. ...")）。
        answer: 正确答案字母（如 "B"）。
        source_nodes: 来源节点 ID 元组。
        difficulty: 难度等级。
        family: 问题家族名称（如 "RETRIEVAL"，v2 出题管线使用，None 表示未指定）。
        skill_target: 目标技能标识（v2 出题管线使用，None 表示未指定）。
        difficulty_steps: 推理步数估计（v2 出题管线使用，None 表示未指定）。
        sub_pattern: 出题子模式标识（AR 特化策略使用，None 表示无）。
        unit_id: 所属题目单元标识；缺省时 __post_init__ 回填为 pair_id 或
            question_id，保证 single 题的 unit_id 等于自身 question_id。
        pair_id: 孪生对标识；同一对的 original/mirror 共享该值，None 表示非配对题。
        question_role: 在单元内的角色（"single" | "pair_original" | "pair_mirror"）。
        flip_axis: 孪生对的翻转轴（如 "before_after"），None 表示无翻转。
    """

    question_id: str
    video_id: str
    task_type: str
    question: str
    options: tuple[str, ...]
    answer: str
    source_nodes: tuple[str, ...]
    difficulty: str
    family: str | None = field(default=None)
    skill_target: str | None = field(default=None)
    difficulty_steps: int | None = field(default=None)
    sub_pattern: str | None = field(default=None)
    unit_id: str = ""
    pair_id: str | None = field(default=None)
    question_role: str = "single"
    flip_axis: str | None = field(default=None)

    def __post_init__(self) -> None:
        """回填 unit_id：缺省时取 pair_id（配对题）或 question_id（single 题）。

        frozen dataclass 无法直接赋值，故通过 object.__setattr__ 绕过不可变约束。
        """
        if not self.unit_id:
            object.__setattr__(self, "unit_id", self.pair_id or self.question_id)


@dataclass(frozen=True)
class QuestionUnit:
    """题目单元：贯穿评测/训练 harness 的最小不可分调度契约实体。

    single 题为 1 题单元，AR pair 孪生对为 2 题单元（original + mirror），
    两条题目必须作为整体被批处理/推理/评测，保证配对指标（collapse 等）可算。
    frozen=True 确保单元不可变。

    属性:
        kind: 单元类型（"single" | "pair"）。
        unit_id: 单元唯一标识；single 取题目 unit_id，pair 取共享 pair_id。
        task_type: 单元题型；pair 内两题题型必须一致。
        questions: 单元内题目元组（single 为 1 条，pair 为 2 条）。
        unit_hash: P/Q payload 合成 hash，用于断点续跑失效检测（T11 消费）。
    """

    kind: str
    unit_id: str
    task_type: str
    questions: tuple[GeneratedQuestion, ...]
    unit_hash: str = ""

    @property
    def size(self) -> int:
        """单元内题目数量（single=1，pair=2）。"""
        return len(self.questions)

    @classmethod
    def from_single(cls, q: GeneratedQuestion) -> QuestionUnit:
        """由单条题目构造 single 单元。

        参数:
            q: 待封装的题目。

        返回:
            kind="single" 的单元，unit_id 取 q.unit_id。
        """
        return cls("single", q.unit_id, q.task_type, (q,))

    @classmethod
    def from_pair(cls, original: GeneratedQuestion, mirror: GeneratedQuestion) -> QuestionUnit:
        """由孪生对（original + mirror）构造 pair 单元。

        参数:
            original: 原始题（question_role="pair_original"）。
            mirror: 镜像题（question_role="pair_mirror"）。

        返回:
            kind="pair" 的单元，unit_id 取共享 pair_id。

        关键实现:
            断言两题共享非空 pair_id、且 video_id/task_type/flip_axis 一致，
            确保只有合法孪生对才能聚合成对，非法配对直接报错而非静默兜底。
        """
        assert original.pair_id and original.pair_id == mirror.pair_id
        assert original.video_id == mirror.video_id and original.task_type == mirror.task_type
        assert original.flip_axis == mirror.flip_axis
        return cls("pair", original.pair_id, original.task_type, (original, mirror))


@dataclass(frozen=True)
class PoolConfig:
    """池构建策略的统一配置。

    两组字段由两个具体策略各取所需，未使用的字段被忽略。

    属性:
        task_types: 限定题型元组；None 表示全部类别。
        seed: 随机种子，保证可复现。
        baseline_run_id: 基线 run 标识。
        diag_size: 诊断池大小（GlobalStrategy 用）。
        diag_correct_ratio: 诊断池中对题占比（GlobalStrategy 用）。
        val_size: 验证池大小（GlobalStrategy 用）。
        val_correct_ratio: 验证池中对题占比（GlobalStrategy 用）。
        test_size: held-out 测试池大小（GlobalStrategy 用）。
        eval_min_per_class: 验证池中每类保底样本数（GlobalStrategy 用）。
        train_ratio: train/(train+val) 比例（PerCategoryStrategy 用）。
        test_questions_dir: 外部 test 题源路径（PerCategoryStrategy 用）。

    实现细节:
        结果驱动视频级切分不复用本配置——它有独立的 VideoSplitConfig /
        SplitBuildConfig / SelectConfig（见 app/harness/video_split_cli.py 与
        split_selection.py），故本类不承载 n_trainval / floor_k / epsilon 等视频级
        切分旋钮，避免死配置面。
    """

    task_types: tuple[str, ...] | None
    seed: int
    baseline_run_id: str
    diag_size: int
    diag_correct_ratio: float
    val_size: int
    val_correct_ratio: float
    test_size: int
    eval_min_per_class: int
    train_ratio: float
    test_questions_dir: _Path | None
    batch_correct_ratio: float | None = None
