"""세계관(visual_world_rules) → 프롬프트 블록 조립 — 공용 SOT.

2026-07-25 사용자 지적("맵도 콘티도 샷 이미지도 한국적이지 않다") 실측:
야외 장소 경로만 세계관이 **한 글자도 도달하지 않았다**.
  - outdoor_place_spec_step: ``rules_text``/``text`` 키를 읽는데 CP 에는
    그런 키가 없다(실제 키=region/era/rules/t2i_context) → 항상 "".
  - outdoor_place_canon_step: ``world_facts_block = ""`` 하드코딩.
    (주석은 '제작자 정정 원문 재주입 금지'가 근거인데, 그건 정정 채널
    이야기이고 세계관 자체는 애초에 주입된 적이 없다 — 별개 결손.)
반면 entity_steps/beat_shot_steps 는 region·era·t2i_context 를 정상
주입한다. 그 관례를 모듈로 승격해 야외 경로에도 같은 사실을 흘린다.

코드는 조립만 한다 — 내용은 전부 CP 데이터(시나리오 의존 하드코딩 0).
"""
from __future__ import annotations

from typing import Any, Dict, Optional

# 이미지 프롬프트에 실을 규칙 종류 — 장소/공간의 물성에 관여하는 것만.
# body_deformation·projection 등 인물/현상 규칙은 장소 T2I 에 넣으면
# 무관 장소까지 오염된다(캐논 정정 원문 오염 실측과 같은 계열).
PLACE_RULE_TYPES = ("time_period", "technology", "architecture",
                    "environment", "culture")


def _vwr_data(cp: Optional[Dict[str, Any]]) -> Dict[str, Any]:
    return (cp or {}).get("data", {}) or {}


def build_world_facts_block(cp: Optional[Dict[str, Any]]) -> str:
    """장소 이미지(캐논 사진/맵) 프롬프트용 세계 사실 블록.

    region·era 는 그 지역·시대의 **실제 건축·시설 관행**을 부르는 앵커다
    (이미지 모델이 "대한민국 2020년대"를 알면 정류장·표지·노면 표기가
    그 나라 것이 된다). 장소 물성에 관여하는 rule 의 visual_guideline
    만 함께 싣고, 인물/현상 규칙은 제외한다. CP 부재/빈 값=빈 문자열
    (호출부 byte-identical).
    """
    d = _vwr_data(cp)
    region = str(d.get("region") or "").strip()
    era = str(d.get("era") or "").strip()
    lines = []
    if region:
        lines.append(f"- Region (real-world reference): {region}")
    if era:
        lines.append(f"- Era: {era}")
    for r in d.get("rules") or []:
        if not isinstance(r, dict):
            continue
        if r.get("rule_type") not in PLACE_RULE_TYPES:
            continue
        guide = str(r.get("visual_guideline") or "").strip()
        if guide:
            lines.append(f"- {guide}")
    if not lines:
        return ""
    return "\n".join(lines)


def build_grounding_world_facts(cp: Optional[Dict[str, Any]]) -> str:
    """★고증(다섯 갈래)용 세계 사실 — **규칙을 갈래로 안 거른다**.

    `build_world_facts_block` 은 **장소 물성**만 남긴다(`PLACE_RULE_TYPES`).
    그것으로 다섯 갈래를 판단하면 **옷을 잃는다** — 실측(2026-08-31, 실제 CP
    61건): `costume` 규칙이 **76건**인데 그 필터에 안 들어간다. `projection`
    76 · `body_deformation` 62 · `transformation` 36 도 마찬가지다. 인물과
    아웃룩은 그 규칙들로 판단해야 한다.

    그래서 여기서는 **있는 규칙을 다 싣되 종류를 밝힌다.** 하류가 어느
    규칙인지 알아야 하고, 코드가 뜻을 미리 재단하지 않는다.

    ★조립만 한다 — 내용은 전부 CP 데이터다(시나리오 의존 하드코딩 0).
    ★빈 값이면 **빈 문자열**을 돌려준다. 부르는 쪽이 fail-closed 한다.
    """
    d = _vwr_data(cp)
    lines = []
    region = str(d.get("region") or "").strip()
    era = str(d.get("era") or "").strip()
    if region:
        lines.append(f"- Region (real-world reference): {region}")
    if era:
        lines.append(f"- Era: {era}")
    ctx = str(d.get("t2i_context") or "").strip()
    if ctx:
        lines.append(f"- Visual context: {ctx}")
    for r in d.get("rules") or []:
        if not isinstance(r, dict):
            continue
        guide = str(r.get("visual_guideline") or "").strip()
        if not guide:
            continue
        kind = str(r.get("rule_type") or "other").strip() or "other"
        lines.append(f"- [{kind}] {guide}")
    return "\n".join(lines)


def grounding_world_facts_ready(cp: Optional[Dict[str, Any]]) -> bool:
    """고증에 쓸 세계 사실이 **성한가**. ★임의 글자수 문턱을 안 쓴다.

    지역·시대 중 하나라도 있고 규칙 지침이 하나라도 있어야 한다 — 그 둘이
    두 축(만들기 어려움 · 알아챔)의 근거다.
    """
    d = _vwr_data(cp)
    has_place = bool(str(d.get("region") or "").strip()
                     or str(d.get("era") or "").strip())
    has_rule = any(isinstance(r, dict)
                   and str(r.get("visual_guideline") or "").strip()
                   for r in (d.get("rules") or []))
    return has_place and has_rule


def build_world_rules_text(cp: Optional[Dict[str, Any]]) -> str:
    """장소 스펙 저작(LLM)용 세계 물리 규칙 텍스트.

    저작 LLM 은 이미지 모델과 달리 규칙 전체를 관련성 판단에 쓸 수
    있으므로 rule_type 필터 없이 전부 싣는다(기존 spec 프롬프트의
    '세계 물리 규칙(참고)' 슬롯 계약 그대로).
    """
    d = _vwr_data(cp)
    parts = []
    region = str(d.get("region") or "").strip()
    era = str(d.get("era") or "").strip()
    if region:
        parts.append(f"지역: {region}")
    if era:
        parts.append(f"시대: {era}")
    for r in d.get("rules") or []:
        if not isinstance(r, dict):
            continue
        desc = str(r.get("description") or "").strip()
        guide = str(r.get("visual_guideline") or "").strip()
        rtype = str(r.get("rule_type") or "").strip()
        if desc or guide:
            head = f"[{rtype}] " if rtype else ""
            parts.append(f"{head}{desc}".strip())
            if guide:
                parts.append(f"  시각 지침: {guide}")
    return "\n".join(parts)
