"""인물 실체 절이 **사람이 그려질 샷에만** 나간다 (2026-08-27, 감사 1-F).

한 프롬프트에 두 문장이 함께 나갔다:

    NO PEOPLE IN THIS SHOT …
    EVERY CHARACTER IS A HUMAN BEING: … When the text gives no direct
    visual description of a person, IMAGINE one …

실측(records 전수 5,508 프롬프트):

    「상상해라」절        3,757 (68%)
    「추가 금지」절        3,721 (68%)
    둘 다                3,207 (58%)
    ★상상해라 + 무인 조항   **427 (8%)**

절의 의도는 분명하다 — 인물이 실루엣·덩어리로 뭉개지는 것을 막는다.
문제는 **조건 없이 붙는다**는 것이고, 무인 샷에서는 그 절이 지킬 인물이
없다.

★「IMAGINE one」의 `one` 도 모호했다. 문맥상 「그 묘사」인데 영어로 읽으면
 「사람 하나」로도 읽힌다.
"""

import re

from app.modules.pipeline.still_recipe import (
    GROK_SHED_ORDER,
    GROK_SHED_STEM_SELECTOR,
    REALIZE_COMPACT_PROMPT_VERSION,
    REALIZE_PROMPT_VERSION,
    STILL_COMPACT_PROMPT_VERSION,
    build_still_prompt,
    fit_grok_prompt,
    grok_shed_policy_signature,
    resolve_prompt_version,
)
from app.modules.prompt_loader import PROMPTS_BASE, load_prompt

_HUMAN = "EVERY CHARACTER IS A HUMAN BEING"
_REALIZE = "REALIZE FIGURATIVE LANGUAGE"
_NO_PEOPLE = "NO PEOPLE"


def _assemble(**kw) -> str:
    base = dict(shot_desc="a man stands", place_text="a room",
                time_of_day_en="night", world_anchor="", prompt_version="1",
                prev_used=False)
    base.update(kw)
    return build_still_prompt(**base)


# ── 갈래 ─────────────────────────────────────────────────────────────

def test_a_person_shot_gets_the_human_clause():
    p = _assemble(bg_only=False)
    assert _HUMAN in p
    assert _REALIZE in p


def test_a_plate_shot_does_not():
    """★이 판의 표적 — 실측 427건이 이 자리다."""
    p = _assemble(bg_only=True)
    assert _NO_PEOPLE in p, "전제 확인 — 무인 조항이 나간다"
    assert _HUMAN not in p, (
        "「사람이 하나도 없다」와 「없으면 상상해서 그려라」가 한 글에 "
        "나갔다")


def test_a_plate_shot_keeps_the_figurative_clause():
    """★걷어내다 **재료까지 지우면 안 된다.**

    비유를 실사로 바꾸라는 절은 **장소 묘사에도** 걸린다 — 무인 샷이라고
    뺄 이유가 없다. 그래서 스템을 둘로 갈랐다.
    """
    assert _REALIZE in _assemble(bg_only=True)


def test_a_person_assignment_brings_the_clause_back():
    """`char_names` 가 `bg_only` 를 뒤집는다(2026-08-06 계약) — 1-C 와 같다."""
    p = _assemble(bg_only=True, char_names=["연희 (한국인 여성)"])
    assert _NO_PEOPLE not in p, "전제 확인 — 인물 배정이 bg_only 를 이긴다"
    assert _HUMAN in p, "사람이 그려질 샷인데 인물 실체 절이 빠졌다"


# ── 모호함 ───────────────────────────────────────────────────────────

def test_the_imagine_sentence_is_no_longer_ambiguous():
    """★`IMAGINE one` 의 `one` 이 「사람 하나」로 읽힐 수 있었다."""
    got = load_prompt(
        "still_recipe", "human_form",
        version=resolve_prompt_version(REALIZE_PROMPT_VERSION))
    assert "IMAGINE one" not in got, "모호한 문장이 남았다"
    assert "a person it puts in" in got, (
        "「그 샷에 있는 사람」이라는 한정이 없다")
    assert "imagine that description" in got, (
        "무엇을 상상하라는지가 사라졌다")


def test_the_clause_still_forbids_the_blob():
    """이 절의 **원래 목적**은 그대로다."""
    got = load_prompt(
        "still_recipe", "human_form",
        version=resolve_prompt_version(REALIZE_PROMPT_VERSION))
    for w in ("shape", "blob", "solid silhouette", "abstract mass"):
        assert w in got, f"실루엣 방지 재료가 사라졌다: {w}"


def test_the_old_pack_is_untouched():
    old = load_prompt("still_recipe", "realize_still",
                      version=resolve_prompt_version("5"))
    assert _HUMAN in old, "발행된 팩을 덮어썼다"


def test_the_new_packs_hold_only_the_two_stems():
    import pathlib

    for sel in (REALIZE_PROMPT_VERSION, REALIZE_COMPACT_PROMPT_VERSION):
        d = (pathlib.Path(PROMPTS_BASE) / "still_recipe"
             / resolve_prompt_version(sel))
        assert sorted(f.name for f in d.glob("*.md")) == [
            "human_form.md", "realize_still.md"], (
            f"v{sel}: 무관한 스템이 딸려 왔다")


def test_the_compact_split_does_not_grow_the_plate_shot():
    """★컴팩트 팩이 있는 이유는 grok 8,000B 상한이다.

    인물 샷은 머리말이 붙어 조금 늘지만(+34B), **무인 샷은 인물 문장이
    통째로 빠져 크게 준다**(-184B).
    """
    import pathlib

    base = pathlib.Path(PROMPTS_BASE) / "still_recipe"
    old = len((base / resolve_prompt_version(STILL_COMPACT_PROMPT_VERSION)
               / "realize_still.md").read_bytes())
    d = base / resolve_prompt_version(REALIZE_COMPACT_PROMPT_VERSION)
    a = len((d / "realize_still.md").read_bytes())
    b = len((d / "human_form.md").read_bytes())
    assert a < old, "무인 샷이 오히려 늘었다"
    assert a + b - old < 100, f"인물 샷이 {a + b - old}B 늘었다 — 너무 크다"


# ── 컴팩트 갈래 ──────────────────────────────────────────────────────

def test_the_compact_branch_gets_the_split_too():
    """★**컴팩트 갈래에 딸려 가면 인물 절이 두 번 나가거나 no-op 이 된다.**

    `guidance_version` 팩에는 옛 통합 `realize_still`(인물 절 포함)이 남아
    있다. 전용 selector 로 갈라 두지 않으면 grok2 에서 이 판이 무의미하다.
    """
    # 전제: 컴팩트 팩의 **옛** 스템은 두 절을 한 문단으로 압축해 둔다 —
    #  머리말이 없어 통째로 두면 무인 샷에서도 인물 문장이 나간다.
    old = load_prompt(
        "still_recipe", "realize_still",
        version=resolve_prompt_version(STILL_COMPACT_PROMPT_VERSION))
    assert "Every character is a real" in old
    assert _HUMAN not in old, "컴팩트 옛 판에는 머리말이 없다 — 전제 확인"

    p = _assemble(bg_only=False, guidance_version=STILL_COMPACT_PROMPT_VERSION)
    assert p.count(_HUMAN) == 1, "인물 절이 두 번 나갔거나 안 나갔다"
    assert p.count(_REALIZE) == 1

    q = _assemble(bg_only=True, guidance_version=STILL_COMPACT_PROMPT_VERSION)
    assert _HUMAN not in q, "grok 무인 샷에 인물 절이 나갔다"


# ── 덜어내기 ─────────────────────────────────────────────────────────

def test_the_shedder_looks_in_the_pack_the_assembly_used():
    """★★**덜어내기는 팩에서 렌더한 정확한 문자열을 지운다.**

    조립이 실은 판과 다른 판을 보면 문자열이 안 맞아 **아무것도 안
    지워지고**, 상한을 넘은 롤이 그대로 죽는다.
    """
    # grok 갈래는 컴팩트 판을 조립하므로 덜어낼 때도 그 판을 본다.
    assert GROK_SHED_STEM_SELECTOR.get("realize_still") == (
        REALIZE_COMPACT_PROMPT_VERSION)

    p = _assemble(shot_desc="a" * 200, place_text="b" * 200, bg_only=False,
                  guidance_version=STILL_COMPACT_PROMPT_VERSION)
    out, shed = fit_grok_prompt(
        p, extra_bytes=0, limit=1600,
        version_selector=STILL_COMPACT_PROMPT_VERSION)
    assert "realize_still" in shed, f"덜어내지 못했다: {shed}"
    assert _REALIZE not in out


def test_the_human_clause_is_never_shed():
    """★인물 절은 **그 샷에 해당할 확률이 100%** 다 — 덜어낼 후보가 아니다."""
    assert "human_form" not in GROK_SHED_ORDER

    p = _assemble(shot_desc="a" * 200, place_text="b" * 200, bg_only=False,
                  guidance_version=STILL_COMPACT_PROMPT_VERSION)
    out, _ = fit_grok_prompt(
        p, extra_bytes=0, limit=1200,
        version_selector=STILL_COMPACT_PROMPT_VERSION)
    assert _HUMAN in out, "상한에 맞추다 인물 실체 절을 잃었다"


def test_the_policy_signature_carries_the_split():
    """정책이 바뀌면 지문이 움직여야 한다 — 안 그러면 옛 그림이 남는다."""
    sig = grok_shed_policy_signature()
    assert re.search(r"realize_still=\d+", sig), (
        f"스템별 팩이 서명에 안 들어갔다: {sig}")
