"""저빈도 스킵 엔티티 ID 파일 I/O 유틸.

`ref_image_gen` step이 T2I 등장 횟수가 낮은 엔티티(1회 이하 + 변형체 base 아님)는
참조 이미지 생성을 비용 절약 목적으로 스킵하고, 그 canon_id 목록을
`projects/{pid}/checkpoints/images/{eid}/ref_low_freq_skip.json`에 기록한다.

이 목록은 여러 소비자가 참조:
  - `pipeline_gate.py` — 이미지 gate 통과 시 저빈도 스킵 엔티티는 미완료로 세지 않음
  - `reference_image_service.py` — 본 step에서 write
  - `api/v1/images.py` — 상태 응답에 `ref_low_freq_skipped` 개수 노출
  - `core/steps/image_steps.py` CompositeImageGenStep — combo_total / existing_keys 계산에서 제외

모두 경로 구성 + read/parse 로직이 복붙돼 있어 중복 → 유틸로 추출.
"""
from __future__ import annotations

import json
import logging
from pathlib import Path
from typing import Iterable, Set

logger = logging.getLogger(__name__)


def get_skip_file_path(project_id: str, episode_id: str) -> Path:
    """저빈도 스킵 파일 경로."""
    from app.core.config import settings
    return (
        Path(settings.projects_dir)
        / project_id
        / "checkpoints"
        / "images"
        / episode_id
        / "ref_low_freq_skip.json"
    )


def load_low_freq_skip_ids(project_id: str, episode_id: str) -> Set[str]:
    """저빈도 스킵된 canon_id 집합 반환. 파일 없거나 파싱 실패 시 빈 set.

    파싱 실패는 warning 로그 — combo_total/미완료 판정에 영향을 줄 수 있어
    운영자가 JSON 손상을 감지할 수 있어야 함.
    """
    skip_file = get_skip_file_path(project_id, episode_id)
    if not skip_file.exists():
        return set()
    try:
        raw = json.loads(skip_file.read_text(encoding="utf-8"))
    except Exception as exc:
        logger.warning(
            "low_freq_skip: %s parse failed: %s", skip_file, exc,
        )
        return set()
    # v1 = plain array of canon_id / v2 = {"version":2,"decisions":[...]}
    if isinstance(raw, dict):
        return {
            d["canon_id"] for d in raw.get("decisions", [])
            if isinstance(d, dict) and d.get("skipped") and d.get("canon_id")
        }
    return set(raw)


def save_low_freq_skip_ids(
    project_id: str, episode_id: str, ids: Iterable[str],
) -> None:
    """저빈도 스킵 canon_id 집합 저장. 부모 디렉토리 자동 생성."""
    skip_file = get_skip_file_path(project_id, episode_id)
    skip_file.parent.mkdir(parents=True, exist_ok=True)
    skip_file.write_text(json.dumps(sorted(set(ids))), encoding="utf-8")


def save_low_freq_skip_report(
    project_id: str, episode_id: str, decisions: Iterable[dict],
) -> None:
    """reasoned object 포맷(v2)으로 저장.

    decisions item = {canon_id, skipped: bool, reason: str, ...}.
    `load_low_freq_skip_ids` 가 v2 dict 도 읽으므로 기존 소비처 무변경.
    """
    skip_file = get_skip_file_path(project_id, episode_id)
    skip_file.parent.mkdir(parents=True, exist_ok=True)
    payload = {
        "version": 2,
        "decisions": sorted(
            (dict(d) for d in decisions),
            key=lambda d: d.get("canon_id", ""),
        ),
    }
    skip_file.write_text(
        json.dumps(payload, ensure_ascii=False), encoding="utf-8",
    )


def load_low_freq_skip_report(project_id: str, episode_id: str) -> dict:
    """reasoned report 반환. v1 array 면 decisions 로 승격, 부재 시 빈 v2."""
    skip_file = get_skip_file_path(project_id, episode_id)
    if not skip_file.exists():
        return {"version": 2, "decisions": []}
    try:
        raw = json.loads(skip_file.read_text(encoding="utf-8"))
    except Exception as exc:
        logger.warning("low_freq_skip: %s parse failed: %s", skip_file, exc)
        return {"version": 2, "decisions": []}
    if isinstance(raw, dict):
        return raw
    return {
        "version": 2,
        "decisions": [
            {"canon_id": cid, "skipped": True, "reason": "(legacy v1)"}
            for cid in raw
        ],
    }
