"""발행 projection — 소스 행을 render-ready payload + 안정 해시로 변환 (순수 함수).

뷰어가 보는 '단일 진실'은 이 payload 까지 (DS 내부 실험 스키마는 계약 뒤 격리).
kind 별 projector. payload_hash = 정렬된 JSON 의 sha256 = (payload_hash, deleted) 디둡 키.

★주의(plan study-to-viewer-slice1 r2): 과목/시험메타를 per-question payload 에 인라인 —
  bulk subject rename 시 N행 churn. 정규화(과목=별 kind subject ref)는 churn 최적화 후속(P0-1b),
  읽기 정합엔 무영향. 지금은 인라인(상관관계 단순)으로 두고 후속 PR 에서 분리.
SCHEMA_VERSION = 엔벨로프 버전. payload 모양 진화 시 bump + 뷰어 range 수용(P0-2).
"""

from __future__ import annotations

import hashlib
import json
from typing import Any

SCHEMA_VERSION = 1

KIND_QUESTION = "study_question"
KIND_EXPLANATION = "study_explanation"
KIND_TOPIC = "study_topic"
KIND_CARD = "study_card"  # ★뷰어 pubstudy.ts 의 KIND_CARD 와 일치 필수(S-3 forward-contract).
KIND_CARD_PROGRESS = "study_card_progress"  # 카드 SR 상태 read model (S-4, viewer C-4 소비).


def payload_hash(payload: dict[str, Any]) -> str:
    """정렬 JSON 의 sha256 — (payload_hash, deleted) 디둡 키. 키 순서/공백 비의존."""
    canonical = json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
    return hashlib.sha256(canonical.encode("utf-8")).hexdigest()


def project_question(q: Any) -> dict[str, Any]:
    """study_question → 발행 payload. 정답 포함(개인 학습툴, plan Q2). 이미지는 ref 만(P0-4, 후속)."""
    return {
        "topic_id": q.study_topic_id,
        "question_text": q.question_text,
        "choices": [q.choice_1, q.choice_2, q.choice_3, q.choice_4],
        "correct_choice": q.correct_choice,
        "subject": q.subject,
        "scope": q.scope,
        "exam_name": q.exam_name,
        "exam_round": q.exam_round,
        "exam_question_number": q.exam_question_number,
        "explanation": q.explanation,  # 수동 해설(있으면). AI 해설은 별 kind.
    }


def project_explanation(q: Any) -> dict[str, Any] | None:
    """study_question 의 AI 해설 → 별 발행 kind. ready 일 때만(없으면 None=발행 안 함).

    재조우 표시용 선발행. 신규 오답은 4-A 워커가 ~90s 후 ready→재발행(P2-3 결선, P0-1b).
    """
    if getattr(q, "ai_explanation_status", None) != "ready" or not getattr(q, "ai_explanation", None):
        return None
    gen = getattr(q, "ai_explanation_generated_at", None)
    return {
        "question_source_id": q.id,
        "explanation_md": q.ai_explanation,
        "model": getattr(q, "ai_explanation_model", None),
        "generated_at": gen.isoformat() if gen else None,
    }


def project_card(c: Any) -> dict[str, Any]:
    """study_memo_card → 발행 payload (S-2). 순수 변환 — 발행 자격(needs_review=false &
    미삭제) 판단은 호출측(enqueue_card_publish)이 카드 상태로. payload 계약 = 뷰어
    pubstudy.ts getCards 와 동형(format·cue·fact·cloze_text·source_question_id·source_generated_at).
    """
    gen = getattr(c, "source_generated_at", None)
    return {
        "format": c.format,
        "cue": c.cue,
        "fact": c.fact,
        "cloze_text": c.cloze_text,
        "source_question_id": c.source_question_id,
        "source_generated_at": gen.isoformat() if gen else None,
    }


def project_card_progress(p: Any) -> dict[str, Any]:
    """study_memo_card_progress → 발행 payload (S-4) = 카드 SR 상태 read model.

    ★ALL row 발행(due_at NULL sentinel=암-on-new · terminal=졸업 포함). due-only 발행하면
    sentinel 누락 → viewer 가 '미확인' 오분류. SR 계산은 DS(sr_schedule), 여긴 결과만.
    card_id = pub_card 의 source_id(=DS card.id) → viewer C-4 가 pub_card LEFT JOIN 하는 키.
    """
    due = getattr(p, "due_at", None)
    rev = getattr(p, "last_reviewed_at", None)
    return {
        "card_id": p.card_id,
        "topic_id": p.study_topic_id,
        "last_outcome": p.last_outcome,
        "last_reviewed_at": rev.isoformat() if rev else None,
        "due_at": due.isoformat() if due else None,
        "review_stage": p.review_stage,
    }


def project_topic(t: Any) -> dict[str, Any]:
    """study_topic → 발행 payload (S-1, plan study-viewer-port).

    topic 메타만 신규 발행 — viewer 가 주제 단위 퀴즈를 만들 최소 정보.
    회차 목록은 발행 안 함 = viewer 가 pub_content(study_question) 의 exam_name/exam_round 로
    파생(추가 발행 불요, plan S-1 결정). topic_id 는 project_question 의 topic_id(=study_topic_id)
    와 동일 DS 식별자라 viewer 가 문항→주제 상관에 사용(pub_id 는 opaque 라 상관 키 아님).
    """
    return {
        "topic_id": t.id,
        "name": t.name,
        "exam_round_size": t.exam_round_size,
    }
