"""dispatch.prompt — task-2386 슬림 prompt facade (task-2388 Phase ε).

dispatch 패키지의 본체는 `dispatch/__init__.py`에 있다.
이 facade는 build_prompt + resume + project_map 영역의 함수만 노출한다.

★ task-2386 슬림 prompt 개선판 보존 — 본체 코드는 무변경.

★ task-2640 (회장 verbatim unfork #3) — prompt 생성 전 ``enforce_callback_owner``
+ ``validate_spawn_callback_contract`` guard 통과를 확인. prompt 텍스트만으로
callback 정책 전달되는 구조 차단. 신규 ``build_prompt_with_contract`` 가
build_prompt 를 감싸고 PASS 시에만 prompt 본문을 반환한다.
"""

from __future__ import annotations

from typing import Optional, Sequence

from dispatch import _inject_project_map_context, _resolve_resume, build_prompt
from dispatch.callback_owner_enforcer import (
    ANU_KEY_2553,
    COLLECTOR_ROLE_ANU,
    DEFAULT_ANU_KEYS,
    PASS,
    enforce_callback_owner,
)
from dispatch.spawn_callback_contract_validator import (
    NO_OP_SPAWN_CONTRACT_FAILED,
    validate_spawn_callback_contract,
)


_CHAIR_FACING_SID_HEADER = "## ANU chair-facing session"


def _inline_chair_facing_sid(prompt_text: str, sid: str) -> str:
    """build_prompt 출력 prompt 끝에 chair-facing SID 마커를 append.

    이미 동일 헤더가 존재하면 멱등 (중복 inline 방지). 산출 라인:
        ## ANU chair-facing session
        chair_facing_session_id: <SID>
        callback_resume_required: true
        ★ callback envelope 작성 시 `cokacdir --cron ... --session <SID>` 강제.

    spawn_callback_contract_validator 의 doctrine token 검사는 ANU key + role
    + SELF_COLLECTOR/SENDFILE_ONLY/NOT_REGISTERED 만 본다 → byte-additive 안전.
    """
    if not sid:
        return prompt_text
    from dispatch.normal_fallback_callback_helper import is_valid_session_id
    if not is_valid_session_id(sid):
        return prompt_text
    if _CHAIR_FACING_SID_HEADER in (prompt_text or ""):
        return prompt_text
    block = (
        f"\n\n{_CHAIR_FACING_SID_HEADER}\n"
        f"chair_facing_session_id: {sid}\n"
        f"callback_resume_required: true\n"
        f"★ callback envelope 작성 시 `cokacdir --cron ... --session {sid}` 강제 "
        f"(★ task-2686 회장 verbatim 2번 / ANCHOR-5 dogfood).\n"
    )
    base = prompt_text or ""
    if base and not base.endswith("\n"):
        base = base + "\n"
    return base + block


def build_prompt_with_contract(
    team_id: str,
    task_desc: str,
    task_id: str,
    level: str = "normal",
    project_id: Optional[str] = None,
    chain_id: Optional[str] = None,
    task_type: str = "coding",
    *,
    executor_key: str,
    anu_key: str = ANU_KEY_2553,
    dispatch_cron_id: str = "spawn-contract-precheck",
    normal_collector_cron_id: Optional[str] = "spawn-contract-precheck-normal",
    fallback_callback_cron_id: Optional[str] = None,
    no_fallback: bool = True,
    anu_keys: Optional[Sequence[str]] = None,
    chat_id: str = "",
    chair_facing_session_id: Optional[str] = None,
) -> dict:
    """prompt 생성 전 callback contract 정합성 enforce guard 적용.

    Two-stage gate (텍스트+코드 이중 안전, task md ANCHOR-4):
      1. ``enforce_callback_owner`` — owner 4-tuple 검증 (collector_role=ANU,
         owner != executor, owner is independent ANU key).
      2. ``validate_spawn_callback_contract`` — 산출 prompt 본문이 anu_key
         텍스트 + collector_role + SELF_COLLECTOR/SENDFILE_ONLY/NOT_REGISTERED
         doctrine 토큰을 포함하는지 단언.

    enforce guard 가 PASS 일 때만 실제 build_prompt 호출 결과를 반환한다.
    FAIL 시 ``status: blocked`` + ``no_op_reason`` 반환 → 호출자는 spawn 진입
    자체를 차단해야 함.

    task-2686 ★ chair-facing session propagation (회장 verbatim 2번):
    ``chair_facing_session_id`` 가 명시되면 산출 prompt 마지막에
    ``## ANU chair-facing session`` 헤더로 SID 를 inline 한다. 봇 (executor)
    는 callback envelope 작성 시 ``cokacdir --session <SID>`` 을 자동 첨부할
    수 있다. 미명시면 prompt 변경 0 (byte-0 backwards compatible).

    Returns:
        dict — {
            "status": "ok" | "blocked",
            "prompt": str | None,
            "enforce_verdict": str,
            "contract_verdict": str,
            "classifications": [..],
            "reasons": [..],
            "no_op_reason": NO_OP_SPAWN_CONTRACT_FAILED (blocked only),
        }
    """
    keys = tuple(anu_keys) if anu_keys is not None else tuple(DEFAULT_ANU_KEYS)

    # Stage 1: pre-build callback owner enforcement.
    enforce_result = enforce_callback_owner(
        task_id=task_id,
        executor_key=executor_key,
        collector_key=anu_key,
        collector_owner_key=anu_key,
        collector_role=COLLECTOR_ROLE_ANU,
        normal_collector_cron_id=normal_collector_cron_id,
        fallback_callback_cron_id=fallback_callback_cron_id,
        dispatch_cron_id=dispatch_cron_id,
        chat_id=chat_id,
        prompt_claims_anu_collector=True,
        entry_path="dispatch.core.main",
        anu_keys=keys,
        no_fallback=no_fallback,
    )
    if enforce_result.verdict != PASS:
        return {
            "status": "blocked",
            "prompt": None,
            "enforce_verdict": enforce_result.verdict,
            "contract_verdict": None,
            "classifications": list(enforce_result.classifications),
            "reasons": list(enforce_result.reasons),
            "no_op_reason": NO_OP_SPAWN_CONTRACT_FAILED,
            "schema": enforce_result.schema,
            "task_id": task_id,
        }

    # Stage 2: build prompt then validate doctrine injection.
    prompt = build_prompt(
        team_id=team_id,
        task_desc=task_desc,
        task_id=task_id,
        level=level,
        project_id=project_id,
        chain_id=chain_id,
        task_type=task_type,
    )

    # task-2686 ★ chair-facing session propagation inline (회장 verbatim 2번).
    # 봇은 본 inline 을 통해 본 task md 에 SID 가 명시되지 않아도 callback
    # envelope 작성 경로에서 cokacdir --session 옵션을 결정할 수 있다.
    if chair_facing_session_id:
        prompt = _inline_chair_facing_sid(prompt, chair_facing_session_id)

    contract_result = validate_spawn_callback_contract(
        task_id=task_id,
        executor_key=executor_key,
        anu_key=anu_key,
        prompt_text=prompt,
        anu_keys=keys,
    )
    if contract_result.verdict != PASS:
        return {
            "status": "blocked",
            "prompt": prompt,
            "enforce_verdict": enforce_result.verdict,
            "contract_verdict": contract_result.verdict,
            "classifications": list(contract_result.classifications),
            "reasons": list(contract_result.reasons),
            "no_op_reason": NO_OP_SPAWN_CONTRACT_FAILED,
            "schema": contract_result.schema,
            "task_id": task_id,
        }

    return {
        "status": "ok",
        "prompt": prompt,
        "enforce_verdict": enforce_result.verdict,
        "contract_verdict": contract_result.verdict,
        "classifications": [],
        "reasons": [
            "build_prompt_with_contract PASS — enforce_callback_owner + "
            "validate_spawn_callback_contract 양단 통과 (텍스트+코드 이중 안전)."
        ],
        "schema": contract_result.schema,
        "task_id": task_id,
    }


__all__ = [
    "_inject_project_map_context",
    "_resolve_resume",
    "build_prompt",
    # task-2640 prompt-time enforce guard (회장 verbatim unfork #3)
    "build_prompt_with_contract",
    # task-2686 chair-facing session propagation inline marker
    "_inline_chair_facing_sid",
]
