"""프롬프트 조립 순서 — 정적 앞, 가변 뒤 (축 A1).

설계 SOT: 프롬프트 최적화 뼈대 v2 의 "축 A1" — **같은 role 안에서 순서만**
바꾼다. role 이동·문구 수정·조각 삭제는 이 층의 일이 아니다(각각 축 A2·C).

바꾸는 것은 한 가지다. 지금은 호출마다 달라지는 조각(대상 이름·타입)이
user 프롬프트 맨 앞에 있어서, 뒤따르는 동일한 지시문이 접두부 재사용에서
벗어난다. 이 모듈은 그 조각을 뒤로 보내 앞쪽을 [정적 지시 → 프로젝트 단위
가변 → 호출 단위 가변] 순서로 만든다.

**무엇이 정적인지 어휘로 판단하지 않는다.** 템플릿을 두 번 렌더한다 —
한 번은 실제 값으로, 한 번은 표식 값으로. 표식이 들어간 문단만 "호출마다
변하는 문단"이고 나머지는 정적이다. 값의 글자를 찾아보는 방식은 쓸 수 없다
(예: `entity_type="prop"` 은 정적 지시문 안에도 그대로 나온다).

기본값은 무변화다. `prompt_assembly_reorder_steps` 목록에 없는 스텝은
기존 조립 결과와 글자 하나까지 같다.
"""
from __future__ import annotations

import logging
from typing import Callable, Dict, List, Sequence, Tuple

logger = logging.getLogger(__name__)

# 문단 구분자 — 이 문자열로 쪼갠 뒤 같은 문자열로 다시 잇는다. 조각의
# 다중집합이 보존되므로 순서만 바뀐다(내용 무손실).
PARA_SEP = "\n\n"

# 렌더 표식. 프롬프트 본문에 나올 수 없는 문자를 쓴다.
_MARK = "\x00"
_MARK_PREFIX = f"{_MARK}PROMPT_VAR_"


def reorder_enabled(step: str) -> bool:
    """이 스텝이 재배열 대상인가 — `prompt_assembly_reorder_steps` 목록 기준."""
    from app.core.config import settings

    raw = getattr(settings, "prompt_assembly_reorder_steps", "") or ""
    return step in {s.strip() for s in raw.split(",") if s.strip()}


def _mark_fields(fields: Dict[str, str]) -> Dict[str, str]:
    return {k: f"{_MARK_PREFIX}{k}{_MARK}" for k in fields}


def split_variable_paragraphs(
    render: Callable[..., str],
    fields: Dict[str, str],
) -> Tuple[List[str], List[str]]:
    """렌더된 템플릿을 (정적 문단, 가변 문단) 으로 가른다.

    두 목록 모두 원래 상대 순서를 유지한다. 두 목록을 원래 자리대로 합치면
    `render(**fields)` 와 같다.

    값 자체에 문단 구분자가 들어오면 두 렌더의 문단 수가 어긋난다. 그때는
    어느 문단이 가변인지 확정할 수 없으므로 raise 한다 — 조용히 기존 순서로
    되돌리면 재배열했다는 기록이 거짓이 된다.
    """
    rendered = render(**fields)
    probe = render(**_mark_fields(fields))

    paras = rendered.split(PARA_SEP)
    probe_paras = probe.split(PARA_SEP)
    if len(paras) != len(probe_paras):
        raise ValueError(
            "프롬프트 조립 재배열 불가 — 값에 문단 구분자가 들어 있어 "
            f"문단 수가 어긋난다 (실제 {len(paras)} vs 표식 {len(probe_paras)})"
        )

    static: List[str] = []
    variable: List[str] = []
    for para, probe_para in zip(paras, probe_paras):
        (variable if _MARK_PREFIX in probe_para else static).append(para)
    return static, variable


def assemble_user_prompt(
    step: str,
    *,
    render: Callable[..., str],
    fields: Dict[str, str],
    project_block: str = "",
    call_block: str = "",
) -> str:
    """user 프롬프트 조립 — 재배열 대상이면 정적 문단을 앞으로 보낸다.

    Args:
        step: 스텝 이름 (플래그 목록과 대조)
        render: `**fields` 를 받아 템플릿을 렌더하는 호출체
        fields: 호출마다 변하는 템플릿 값
        project_block: 프로젝트·주행 단위로 고정인 덧붙임 블록
        call_block: 호출마다 변하는 덧붙임 블록

    OFF: `render(**fields) + project_block + call_block` — 기존 조립 그대로.
    ON:  [정적 문단] + project_block + [가변 문단] + call_block.
    """
    base = render(**fields)
    if not reorder_enabled(step):
        return base + project_block + call_block

    static, variable = split_variable_paragraphs(render, fields)
    out = PARA_SEP.join(static) + project_block
    if variable:
        out += PARA_SEP + PARA_SEP.join(variable)
    return out + call_block


def paragraph_multiset(text: str) -> Sequence[str]:
    """조립 결과의 문단 다중집합 — 재배열 전후 무손실 대조용(시험 전용)."""
    return sorted(text.split(PARA_SEP))
