"""공부 암기노트 카드별 가드 — 추출된 카드 1장 검증 파이프라인.

explanation 워커의 단일 answer_choice 환각가드를 카드 배열로 확장한다. 가드 4종:
  1. 형식 유효성 — format in {qa, cloze}, cue/fact 비공백, cloze 는 cloze_text + 빈칸 마커 필요.
  2. 근거(hallucination) — 정답토큰(fact)이 신뢰 텍스트에 등장해야 채택.
       정량 토큰(숫자 포함): evidence 원문 snippet 에 등장 필수 (평문화된 ai_explanation 만으론 불충분).
       비정량(개념): ai_explanation 또는 evidence snippet 에 등장.
  3. 누출 — cue 에 정답 노출 / cloze 평문에 정답 노출 시 drop.
  4. dedup — (source_question_id, format, normalize(정답토큰)) hash. 배치 내 중복 1장.

무결성은 구조로(메모리 규칙): dedup_hash PARTIAL UNIQUE(migration 288)가 DB 최종 방어선,
본 가드는 1차. 전부 drop 이면 빈 리스트 → 워커가 all_dropped 로 종결.
"""

from __future__ import annotations

from dataclasses import dataclass, field

from services.study import card_normalize as cn

_VALID_FORMATS = {"qa", "cloze"}


@dataclass
class GuardedCard:
    format: str
    cue: str
    fact: str
    cloze_text: str | None
    dedup_hash: str
    matched_evidence: list[dict] = field(default_factory=list)


def guard_card(
    card: dict,
    *,
    source_question_id: int | None,
    ai_explanation: str | None,
    evidence_refs: list[dict],
) -> GuardedCard | None:
    """카드 1장 검증. 통과하면 GuardedCard, 탈락하면 None."""
    fmt = (card.get("format") or "").strip()
    cue = (card.get("cue") or "").strip()
    fact = (card.get("fact") or "").strip()
    cloze_text = card.get("cloze_text")
    cloze_text = cloze_text.strip() if isinstance(cloze_text, str) else None

    # 1. 형식 유효성
    if fmt not in _VALID_FORMATS or not cue or not fact:
        return None
    if fmt == "cloze":
        if not cloze_text or not cn._BLANK.search(cloze_text):
            return None

    # 3. 누출 (정답 노출)
    if cn.is_cue_leak(cue, fact):
        return None
    if fmt == "cloze" and cn.is_cloze_self_leak(cloze_text, fact):
        return None

    # 2. 근거 (hallucination 차단)
    matched = cn.matching_evidence(fact, evidence_refs)
    if cn.is_quantitative(fact):
        # 정량 토큰은 evidence 원문 등장 필수
        if not matched:
            return None
    else:
        # 비정량은 ai_explanation 또는 evidence 에 등장
        if not matched and not cn.text_contains(ai_explanation, fact):
            return None

    return GuardedCard(
        format=fmt,
        cue=cue,
        fact=fact,
        cloze_text=cloze_text if fmt == "cloze" else None,
        dedup_hash=cn.compute_dedup_hash(source_question_id, fmt, fact),
        matched_evidence=matched,
    )


def guard_cards(
    cards: list[dict],
    *,
    source_question_id: int | None,
    ai_explanation: str | None,
    evidence_refs: list[dict],
) -> list[GuardedCard]:
    """카드 배열 검증 + 배치 내 dedup_hash 중복 1장. 통과 카드만 반환."""
    out: list[GuardedCard] = []
    seen: set[str] = set()
    for card in cards or []:
        if not isinstance(card, dict):
            continue
        g = guard_card(
            card,
            source_question_id=source_question_id,
            ai_explanation=ai_explanation,
            evidence_refs=evidence_refs,
        )
        if g is None or g.dedup_hash in seen:
            continue
        seen.add(g.dedup_hash)
        out.append(g)
    return out
