"""몸이 곧 신원인 인물 — **한 자리에서** 답한다 (2026-09-18).

로봇·사이보그·기계 몸·외계 생명처럼 **얼굴만 봐서는 같은 인물인지 알 수 없는**
인물이 있다. 이들은 아웃룩(덧입는 옷)이 있어도 기본 참조가 **전신**이어야 하고,
아웃룩 합성본은 그 전신을 **그대로 두고** 파생시켜야 한다.

★왜 helper 로 빼는가 — 배치 생성(reference_pipeline_orchestrator)과 **수동
 재생성**(reference_entity_service)이 **같은 판정**을 읽어야 한다. 한쪽만 알면
 손으로 다시 만든 참조가 흉상으로 돌아간다(Codex 2026-09-18 지적).

판정은 `outlook_phase1` 이 구조화해 답하고(`body_identity_chars`), 여기서는
그 답을 읽기만 한다 — 글자로 뜻을 판단하지 않는다.
"""
from __future__ import annotations

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

logger = logging.getLogger(__name__)

_STEP = "outlook_phase1"
_KEY = "body_identity_chars"


def _episode_dirs(project_id: str, episode_id: Optional[str]) -> list:
    from app.core.config import settings

    base = Path(settings.projects_dir) / project_id / "checkpoints" / "episodes"
    if episode_id:
        return [base / episode_id]
    if not base.exists():
        return []
    return sorted(d for d in base.iterdir() if d.is_dir())


def body_identity_short_ids(
    project_id: str, episode_id: Optional[str] = None,
) -> Set[str]:
    """이 프로젝트에서 **몸이 곧 신원**으로 판정된 인물의 short_id.

    `episode_id` 를 주면 그 화의 판정만, 안 주면 이 프로젝트의 모든 화를 합친다
    (수동 재생성은 화 맥락이 없다 — 인물의 성질이지 그 화의 성질이 아니다).
    """
    out: Set[str] = set()
    for d in _episode_dirs(project_id, episode_id):
        f = d / _STEP / "manifest.json"
        if not f.exists():
            continue
        try:
            data = (json.loads(f.read_text(encoding="utf-8")).get("data") or {})
        except Exception as exc:                      # noqa: BLE001
            logger.warning("body_identity: %s 를 못 읽었다: %s", f, exc)
            continue
        out.update(str(s) for s in (data.get(_KEY) or []) if s)
    return out


def body_identity_entity_ids(
    db, project_id: str, episode_id: Optional[str] = None,
) -> Set[str]:
    """위 판정을 **entity id** 로 옮긴다. 판정이 없으면 빈 집합(종전 동작)."""
    sids = body_identity_short_ids(project_id, episode_id)
    if not sids:
        return set()
    from app.models.project import EntityCanon

    rows = (
        db.query(EntityCanon)
        .filter(EntityCanon.project_id == project_id,
                EntityCanon.entity_type == "character")
        .all()
    )
    return {e.id for e in rows if e.short_id in sids}


def reference_entity_type(base_entity_type: str, *, is_body_identity: bool,
                          is_null_outlook: bool = False) -> str:
    """참조 이미지를 만들 때 쓸 타입 — **전신이냐 흉상이냐를 여기서 가른다.**

    ★두 물음이 한 줄에 뭉쳐 있던 것이 이번 결함의 뿌리다:
     ①이 인물에 옷이 있나(`is_null_outlook`) ②몸이 곧 신원인가.
     로봇 주인공은 둘 다 「예」라 옷이 있다는 이유로 흉상 경로로 갔다.
    """
    if base_entity_type == "character" and (is_body_identity or is_null_outlook):
        return "character_nonhuman"
    return base_entity_type
