"""QuestionUnit 组装/展开/校验/单元正确性——pair 契约的唯一入口。

pool 构建、批处理、推理、评测（Task 3+）均通过本模块聚合/展开孪生对，
保证 AR pair 的"两题作为整体调度"契约只在一处实现、fail-fast 暴露非法配对。
"""

from collections import defaultdict

from core.types import GeneratedQuestion, QuestionUnit


def _assemble_pair(pair_id: str, group: list[GeneratedQuestion]) -> QuestionUnit:
    """校验单个 pair 分组的数量/角色并组装为 pair 单元（fail-fast）。

    参数:
        pair_id: 该分组共享的孪生对标识。
        group: 归属同一 pair_id 的题目列表。

    返回:
        kind="pair" 的 QuestionUnit。

    关键实现:
        - 分组必须恰好 2 条，否则视为孤儿/超员，raise ValueError。
        - 显式检查 pair_original / pair_mirror 角色齐备且唯一，缺失或重复
          直接 raise ValueError（防 next(...) 静默 StopIteration）。
        - 合法孪生对交由 QuestionUnit.from_pair 做 video_id/task_type/flip_axis
          一致性断言。
    """
    if len(group) != 2:
        raise ValueError(f"pair {pair_id} 数量={len(group)}≠2（孤儿或超员）")
    originals = [q for q in group if q.question_role == "pair_original"]
    mirrors = [q for q in group if q.question_role == "pair_mirror"]
    if len(originals) != 1 or len(mirrors) != 1:
        raise ValueError(
            f"pair {pair_id} 角色非法：original={len(originals)} mirror={len(mirrors)}，"
            "需各恰好 1 条"
        )
    return QuestionUnit.from_pair(originals[0], mirrors[0])


def build_units(questions: list[GeneratedQuestion]) -> list[QuestionUnit]:
    """将扁平题目列表聚合为单元列表：single 单封、pair 按 pair_id 成对聚合。

    参数:
        questions: 待聚合的题目列表，可混含 single 与孪生对成员。

    返回:
        单元列表，先 single 后 pair，顺序稳定（single 保留输入顺序，
        pair 按首次出现的 pair_id 顺序）。

    关键实现:
        pair_id 为空 → single 单元；非空 → 归入对应 pair 桶。各 pair 桶的
        数量/角色校验与组装下沉到 _assemble_pair（fail-fast），本体只做分组
        与派发。
    """
    by_pair: dict[str, list[GeneratedQuestion]] = defaultdict(list)
    singles: list[QuestionUnit] = []
    for q in questions:
        if q.pair_id:
            by_pair[q.pair_id].append(q)
        else:
            singles.append(QuestionUnit.from_single(q))

    pairs = [_assemble_pair(pid, qs) for pid, qs in by_pair.items()]
    return singles + pairs


def flatten_units(units: list[QuestionUnit]) -> list[GeneratedQuestion]:
    """将单元列表无损展开回扁平题目列表。

    参数:
        units: 单元列表。

    返回:
        展开后的题目列表，保持单元顺序及单元内题目顺序。
    """
    return [q for u in units for q in u.questions]


def validate_units(units: list[QuestionUnit]) -> list[QuestionUnit]:
    """校验单元列表结构合法性，通过则原样返回（便于链式调用）。

    参数:
        units: 待校验单元列表。

    返回:
        校验通过的原单元列表。

    关键实现:
        pair 单元必须恰好含 2 题，否则 raise ValueError；single 单元无需额外
        校验（构造时即为 1 题）。用于消费方在使用前做一道防御闸门。
    """
    for u in units:
        if u.kind == "pair" and u.size != 2:
            raise ValueError(f"unit {u.unit_id} pair 不成对（size={u.size}）")
    return units


def unit_correctness(unit: QuestionUnit, per_q: dict[str, bool]) -> bool:
    """计算单元级正确性：AR pair 走双向 AND，single 即单题正确性。

    参数:
        unit: 目标单元。
        per_q: 题目 question_id → 该题是否作答正确的映射。

    返回:
        单元内所有题目均正确时为 True，否则 False。

    关键实现:
        直接以 per_q[q.question_id] 取值，缺任一题触发 KeyError（防静默兜底），
        强制上游先补齐全部单题结果再计单元正确性。
    """
    return all(per_q[q.question_id] for q in unit.questions)


def unit_correctness_view(units: list[QuestionUnit], per_q: dict[str, bool]) -> dict[str, bool]:
    """把逐题对错折叠成单元级视图：unit_id → 单元是否整体正确。

    进化引擎（gate e-process / quadrant / probation / pair_block / compute_accuracy）
    统一消费此单元视图，保证 AR pair 双向 AND、非 AR single 单题，混格池中
    孪生对折叠为一个单元、不被 P/Q 单题计分污染（核心算法保真 #5）。

    参数:
        units: 目标单元列表（single 或 pair）。
        per_q: 题目 question_id → 该题是否作答正确（唯一逐题溯源来源）。

    返回:
        unit_id → 单元级正确性。single 的 unit_id 等于其 question_id，
        pair 的 unit_id 等于共享 pair_id。

    关键实现:
        逐单元复用 unit_correctness（内部以 per_q[q.question_id] 取值，缺任一题
        触发 KeyError），禁静默兜底、强制上游先补齐全部单题结果。
    """
    return {u.unit_id: unit_correctness(u, per_q) for u in units}
