"""visual_context_helper — Phase 8: world_guide + visual_world_rules → LLM 가독 text.

Phase 7까지 background_chain pipeline 4 step (background_classify,
background_master_plan, floor_plan_prompt, background_prompt)이
``visual_world_rules`` 체크포인트만 읽고, 그것도 ``data.rules_text`` 라는
존재하지 않는 키만 들여다봐서 결과적으로 LLM에 빈 문자열을 넣었음. 또
``world_guide`` 체크포인트(world_setting_summary, era/costume/prohibited/
continuity/style guardrails 등 시나리오 specific cultural cues가 풍부)는
아예 참조되지 않았음.

이 helper는 두 cp의 데이터를 통합하여 시나리오 의존 hardcode 0를 유지하면서
LLM 가독 markdown block을 산출한다. 작품 고유명사가 본문에 등장하는 것은
체크포인트 데이터에서 그대로 흘러나오는 경우뿐 — 코드/system prompt에는
어떤 작품 specific 단어도 들어가지 않는다.
"""
from __future__ import annotations

from typing import Any, Dict, List, Optional


def _bullet_block(label: str, items: List[Any]) -> Optional[str]:
    items = [str(x).strip() for x in (items or []) if str(x).strip()]
    if not items:
        return None
    body = "\n".join(f"- {x}" for x in items)
    return f"### {label}\n{body}"


def _kv_block(label: str, value: Any) -> Optional[str]:
    s = str(value or "").strip()
    if not s:
        return None
    return f"### {label}\n{s}"


def build_visual_context_block(
    *,
    world_guide_cp: Optional[Dict[str, Any]] = None,
    visual_world_rules_cp: Optional[Dict[str, Any]] = None,
) -> str:
    """두 cp의 시나리오 visual context를 markdown block으로 직렬화.

    파라미터 둘 다 None 또는 빈 dict면 빈 문자열 반환. 어느 한쪽만 있어도
    가능한 정보는 모두 inject한다.
    """
    parts: List[str] = []

    wg = (world_guide_cp or {}).get("data") or {}
    if wg:
        # world_guide_generator schema 기준 필드 (모듈 docstring 참고)
        kv = [
            ("World setting summary", wg.get("world_setting_summary")),
            ("World time period", wg.get("world_time_period")),
            ("Technology level", wg.get("technology_level")),
        ]
        for label, value in kv:
            block = _kv_block(label, value)
            if block:
                parts.append(block)

        list_blocks = [
            ("Era guardrails", wg.get("era_guardrails")),
            ("Costume guardrails", wg.get("costume_guardrails")),
            ("Prop guardrails", wg.get("prop_guardrails")),
            ("Location guardrails", wg.get("location_guardrails")),
            ("Prohibited visual misreads", wg.get("prohibited_visual_misreads")),
            ("Continuity guardrails", wg.get("continuity_guardrails")),
            ("Image generation notes", wg.get("image_generation_notes")),
        ]
        for label, items in list_blocks:
            block = _bullet_block(label, items or [])
            if block:
                parts.append(block)

        sr = wg.get("style_rules") or {}
        if isinstance(sr, dict):
            mm = _bullet_block("Style — must maintain", sr.get("must_maintain") or [])
            ma = _bullet_block("Style — must avoid", sr.get("must_avoid") or [])
            if mm:
                parts.append(mm)
            if ma:
                parts.append(ma)

    vwr = (visual_world_rules_cp or {}).get("data") or {}
    if vwr:
        kv = [
            ("Era", vwr.get("era")),
            ("Region", vwr.get("region")),
            ("Source language", vwr.get("source_language")),
            ("T2I style context", vwr.get("t2i_context")),
        ]
        for label, value in kv:
            block = _kv_block(label, value)
            if block:
                parts.append(block)

        rules = vwr.get("rules") or []
        rule_lines: List[str] = []
        for r in rules:
            rt = (r.get("rule_type") or "rule").strip()
            desc = (r.get("description") or "").strip()
            guide = (r.get("visual_guideline") or "").strip()
            sep = " → " if guide else ""
            rule_lines.append(f"[{rt}] {desc}{sep}{guide}".strip())
        block = _bullet_block("Visual rules", rule_lines)
        if block:
            parts.append(block)

        notes = _bullet_block(
            "Director notes (physical presence)",
            vwr.get("director_notes") or [],
        )
        if notes:
            parts.append(notes)

    return "\n\n".join(parts)


def _detect_lang_from_text(text: str) -> Optional[str]:
    """텍스트의 unicode 범위로 ISO 639-1 코드 추론. 시나리오 의존 hardcode 0.

    - Hangul(가-힣, ㄱ-ㆎ) → ko
    - Hiragana/Katakana(\\u3040-\\u30ff) → ja (단 Hangul 우선 검사 후)
    - 일반 CJK Unified Ideographs(\\u4e00-\\u9fff) → zh
    - 기타 → en
    빈 문자열이면 None.
    """
    if not text:
        return None
    has_hangul = any(
        ("가" <= c <= "힯") or ("ㄱ" <= c <= "ㆎ")
        for c in text
    )
    if has_hangul:
        return "ko"
    has_kana = any(("぀" <= c <= "ヿ") for c in text)
    if has_kana:
        return "ja"
    has_cjk = any(("一" <= c <= "鿿") for c in text)
    if has_cjk:
        return "zh"
    # 영문자/숫자/공백만 또는 그 외 라틴 → en (default)
    has_letter = any(c.isalpha() for c in text)
    if has_letter:
        return "en"
    return None


def extract_source_language(
    visual_world_rules_cp: Optional[Dict[str, Any]] = None,
) -> Optional[str]:
    """visual_world_rules cp.data.source_language → ISO 639-1 코드 또는 None.

    Phase 8.1:
    1. ``data.source_language`` 가 명시되어 있으면 그대로 사용 (vwr v4+).
    2. 누락이면 ``era`` + ``region`` 텍스트의 unicode 범위로 자동 감지
       (vwr v3 호환 — 기존 EP 의 cp 도 자연스럽게 ko/ja/zh/en 추론).
    """
    if not visual_world_rules_cp:
        return None
    data = visual_world_rules_cp.get("data") or {}
    sl = data.get("source_language")
    if isinstance(sl, str) and sl.strip():
        return sl.strip().lower()
    # vwr v3 호환 fallback — era/region 의 글자 범위로 자동 감지
    blob = " ".join(
        str(data.get(k, "") or "")
        for k in ("era", "region", "t2i_context")
    ).strip()
    return _detect_lang_from_text(blob)
