"""다섯 갈래 **공용** 검색 지시문 저작. ★검색·다운로드·판정은 재사용한다.

## 왜 따로 있나 (Codex BLOCK 2026-08-31)

중앙 참조 획득은 `prop`·`character`·`location`·`location_part`·`outlook`
다섯 갈래를 **같은 경로**로 태운다. 그런데 그때까지 쓰던 저작 팩
(`search_grounded_ref/…/brief_system.md`)은 **야외 구조물 전용**이었다 —

    "how a location's fixed structures are really built"
    "THE STRUCTURE that must be built:"
    "the one thing that has to be recognisable for **the place** to read
     as itself"

이 계약으로 소품·인물·아웃룩·장소 부분을 물으면 저작기가 **대상을 장소
구조물로 다시 읽는다.** 그래서 여기서는 **갈래를 런타임 값으로 받는** 일반
계약을 쓴다. 야외 구조물 경로는 옛 팩을 **그대로** 쓴다(보존).

## 무엇을 재사용하나

    schema      `search_grounded_ref.build_search_brief_schema`  ← 그대로
    검색        `search_grounded_ref.search_reference_images`
    다운로드     `search_grounded_ref.download_candidate`
    원문 발췌 길이 `search_grounded_ref._SOURCE_LANGUAGE_SAMPLE_CHARS`

산출 모양이 같으므로 schema 를 **한 곳**에 둔다 — 두 벌이면 한쪽만 고쳐진다.

## 갈래는 코드가 판단하지 않는다

`owner_type` 은 **owner enum 의 값**을 그대로 넘긴다. 갈래마다 다른 문안을
코드가 고르지 않는다 — 그것이 곧 갈래별 하드 프롬프트다. 팩은 「너는 갈래
이름표를 받는다」까지만 말하고, 무엇을 쓸지는 모델이 그 값으로 정한다.
"""
from __future__ import annotations

from typing import Any, Dict, Optional

#: 팩 좌표. ★프롬프트 파일은 덮어쓰지 않는다 — 새 버전 디렉토리로 간다.
PACK_MODULE = "grounding_ref_brief"
#: ★2026-09-02 판 3 — 시대·지역을 **독립**으로 다루고(한쪽만 선언돼도 그것만
#:  싣는다 · 없는 것은 지어내지 않는다), 「원고 언어 = 배경 나라의 언어」라는
#:  단정을 걷었다. 현대 원고에서 시대가 없다고 대상에서 빼지 않는다.
#:  ★옛 판은 그대로 둔다 — 옛 산출의 신원이 그 판을 가리킨다.
PACK_VERSION = "5"
#: ★1 은 **원리 대신 예시·금지 목록**을 담고 있었다 (Codex BLOCK 2026-08-31)
#:  — 특정 실재 대상(브랜드·알려진 장소)을 금지해 사용자가 든 **예외 통로를
#:  지웠다.** 2 는 「받은 그대로의 구체성을 유지한다」는 원리만 남긴다.
PACK_VERSION_MAP: Dict[str, str] = {"1": "1.202608311800",
                                    "2": "2.202608311930",
                                    "3": "3.202609020400",
                                    # ★4 (2026-09-02): 뼈대만 찾는다(마모·색·작은 부속은 뒤 수정 몫) ·
                                    #  실물을 찍은 사진(소장품·복원품)은 받고 그림·삽화만 뺀다 ·
                                    #  2차는 **넓히되** 시대·지역은 그대로.
                                    "4": "4.202609022200",
                                    # ★5 (2026-09-02, Codex BLOCK): 4 의 「현대 장소」 문단에 고정
                                    #  명사 예시가 들어 있었다 — 원리만 남기고 예시는 fixture/시험으로.
                                    "5": "5.202609022330"}


class UnknownOwnerType(ValueError):
    """갈래 이름표가 owner enum 밖이다. ★짐작해서 넘기지 않는다."""


def resolve_pack_version(version: str = PACK_VERSION) -> str:
    v = str(version or PACK_VERSION)
    if v in PACK_VERSION_MAP:
        return PACK_VERSION_MAP[v]
    if v in PACK_VERSION_MAP.values():
        return v
    raise ValueError(f"{PACK_MODULE} 팩 버전 없음: {version}")


def _load(name: str, version: str) -> str:
    from app.modules.prompt_loader import load_prompt

    return load_prompt(PACK_MODULE, name,
                       version=resolve_pack_version(version)).strip()


def load_brief_system(*, prompt_version: str = PACK_VERSION) -> str:
    return _load("brief_system", prompt_version)


def load_narrow_retry_hint(*, prompt_version: str = PACK_VERSION) -> str:
    """좁힘 재검색 힌트.

    ★★옛 판은 **스스로 모순**이었다 — 「지역·**시대**·재질·색을 버려라」와
    「시대 좌표를 지켜라」가 같은 파일에 있었다(내가 시대 문단을 덧붙이면서
    만들었다). 여기서는 **시대만 남기고 나머지 수식을 버린다**로 하나가 됐다.
    """
    return _load("narrow_retry_hint", prompt_version)


def build_brief_schema() -> Dict[str, Any]:
    """★`search_grounded_ref` 것을 **그대로** 쓴다 — 산출 모양이 같다."""
    from app.modules.pipeline.search_grounded_ref import (
        build_search_brief_schema)

    return build_search_brief_schema()


def build_brief_user(
    *,
    owner_type: str,
    coarse_type_label: str,
    subject_description: str,
    world_facts_block: str,
    era_declaration: str = "",
    region_declaration: str = "",
    source_text: str = "",
    researched_facts_block: str = "",
) -> str:
    """저작 입력 조립. ★갈래·부류·서술을 **런타임 값**으로 넘긴다.

    Args:
        owner_type: owner enum 의 값. ★enum 밖이면 선다.
        coarse_type_label: 추출이 낸 **거친 부류 이름**.
        subject_description: 대상 서술(표기 + 생김새 요약).
        world_facts_block: 창작자 확정 세계 사실.
        era_declaration: **구조화된 시대 칸**(`visual_world_rules.era`).
            ★세계 사실 전문에서 짐작하지 않는다 — 제 칸이 있다.
        region_declaration: **구조화된 지역 칸**(`visual_world_rules.region`).
            ★★2차 검색에서 지역을 버리면 **아무도 안 본다** — 검색은
            질의·이미지만 돌려주고 심판은 부류와 보임만 본다(Codex BLOCK
            2026-08-31). 그래서 지역은 시대와 함께 **질의에 남는다**.
        source_text: **언어 판정 전용**. 아래 발췌 예외 설명을 볼 것.
        researched_facts_block: 사전 조사 산출이 있으면.

    ★`source_text` 는 `search_grounded_ref` 와 **같은 상수**로 발췌한다.
    프로젝트 절대 규칙은 「LLM 에 전달하는 시나리오 텍스트를 자르지 마라」
    이지만 사용자가 2026-08-03 에 조건부 예외를 열었다 — *"단순한 정보를
    얻기 위해서 전체를 넣는 것보다 부분만 넣어도 되는 경우에는 예외로
    하자"*. 이 블록의 용도가 **`source_language` 판정 하나뿐**이고 지시문이
    그것을 못박으므로 그 조건에 해당한다. 오히려 전문을 실으면 인물·사건·
    임시 사물·작품 고유명사가 검색 지시문으로 새는 표면이 커진다.
    """
    from app.modules.pipeline.grounding_entity_contract import owners
    from app.modules.pipeline.search_grounded_ref import (
        _SOURCE_LANGUAGE_SAMPLE_CHARS)

    ot = str(owner_type or "").strip()
    if ot not in set(owners()):
        raise UnknownOwnerType(
            f"갈래 {ot!r} 는 owner enum 밖이다 (있는 것: "
            f"{sorted(owners())}) — 짐작해서 저작하지 않는다")
    desc = str(subject_description or "").strip()
    if not desc:
        raise ValueError("subject_description 결손 — 검색 대상 서술 필수")
    kind = str(coarse_type_label or "").strip()
    if not kind:
        raise ValueError("coarse_type_label 결손 — 부류 이름표 필수")

    facts = (researched_facts_block or "").strip()
    era = str(era_declaration or "").strip()
    region = str(region_declaration or "").strip()
    sample = (source_text or "")[:_SOURCE_LANGUAGE_SAMPLE_CHARS]
    return (
        "SEARCH SEMANTICS COME FROM THE BLOCKS ABOVE THE LINE ONLY.\n\n"
        "REGION AND PERIOD (creator-confirmed):\n"
        + (world_facts_block or "")
        + (("\n\nTHE PERIOD, as the creator declared it: " + era)
           if era else "")
        + (("\nTHE REGION, as the creator declared it: " + region)
           if region else "")
        + "\n\nTHE KIND this subject is registered as: " + ot
        + "\nITS COARSE TYPE: " + kind
        + "\n\nTHE SUBJECT that must be drawn:\n" + desc
        + (("\n\n" + facts) if facts else "")
        + "\n\n---\nSOURCE TEXT SAMPLE (opening excerpt) — THIS BLOCK OWNS "
          "`source_language` AND NOTHING ELSE. It must not contribute "
          "people, events, incidents, layout, or any story-specific name to "
          "the search instruction you write. Read it only to decide which "
          "language to write in:\n" + sample
    )


def brief_contract(*, prompt_version: str = PACK_VERSION) -> Dict[str, str]:
    """저작 계약 — **무엇으로 지시문을 쓰는가**. 장부·잠금에 그대로 실린다."""
    return {"writer": PACK_MODULE,
            "pack": resolve_pack_version(prompt_version)}
