"""나가는 호출의 **층 계약** — counted 와 raw 는 다르다. ★한 곳에서만 센다.

Codex BLOCK 2 (2026-08-31) —

> `physical_per_logical` 은 key-slot/tier/router retry 만 셉니다.
> `ref_canary.py:441-487` 이 이미 기록했듯 structured 경로의 LiteLLM SDK
> `max_retries=2` 는 reserve **아래**라 raw HTTP 가 counted 보다 최대 3배입니다.
> **같은 산식을 새로 쓰지 말고** 기존 층 계산을 공용 계약으로 추출해
> 재사용하십시오.

## 층 셋

    ①logical          우리가 「한 번 부른다」고 세는 수
    ②counted          `reserve` 가 실제로 세는 provider 시도.
                      ★상한을 **거는** 수다 — 넘으면 네트워크 전에 선다
    ③raw_http         진짜 HTTP 요청 수. litellm 이 제 client 에 주는
                      `max_retries` 가 `reserve` **아래**라 안 세어진다

    counted = logical × tiers × key_slots × router_tries
    raw     = counted × (1 + litellm_sdk_max_retries)

★`tiers` 는 `enable_fallback` 이 켜져 있을 때만 3이다. `router_tries` 는
`num_retries` 로 정해진다. 둘 다 **요청 계약**에서 온다 — 계약이 바뀌면 수가
바뀐다.
"""
from __future__ import annotations

from typing import Any, Dict, Optional

#: `call_structured` 의 tier 수. ★`enable_fallback` 이 꺼지면 1이다.
CALL_STRUCTURED_TIERS = 3

#: ★`litellm.completion()` 이 `inference_params.pop("max_retries", 2)` 로 집는
#:  값. `call_structured` 공개 인자에는 `num_retries` 만 있어 **못 바꾼다**.
#:  그리고 `_completion` 의 reserve 는 `router.completion` **바깥**이라
#:  이 겹을 **못 센다**.
LITELLM_SDK_MAX_RETRIES = 2


def sdk_retries_of(contract: Dict[str, Any],
                   default: int = LITELLM_SDK_MAX_RETRIES) -> int:
    """이 계약이 잠근 **SDK 재시도**. ★안 적혀 있으면 기본(2)이다.

    ★★2026-09-02 실측으로 뒤집힌 것 — 앞 주석은 「`call_structured` 공개
    인자에 없어 **못 바꾼다**」였다. 실제로는 `max_retries` 를 kwargs 로
    내려보내면 **지어지는 client 가 그 값을 받는다**(무료 probe: 기본 2 ·
    준 값 0). 그래서 canary 는 잠글 수 있고, 잠갔으면 여기 적는다.
    """
    got = contract.get("sdk_max_retries")
    return default if got is None else max(0, int(got))


def layers(*, contract: Dict[str, Any], slots: Optional[int],
           sdk_max_retries: Optional[int] = None) -> Dict[str, Any]:
    """한 논리 호출이 여는 층. ★슬롯을 모르면 **지어내지 않는다**.

    ★★★**문 위와 아래를 가른다** (2026-08-31 실측) —

        문 **위**(세어진다)   tier × 키 슬롯
            `call_structured` 가 tier 마다 `_completion` 을 **다시** 부르고
            (`llm_client.py:1230·1334·1445`), `_completion` 은 슬롯 loop
            **안에서** 매번 `reserve` 한다
        문 **아래**(안 세어진다)  (1+router num_retries) × (1+SDK max_retries)
            Router 는 `num_retries` 로 **`router.completion()` 안에서** 다시
            보내고(`llm_client.py:657`), litellm 은 제 client 에 `max_retries`
            를 준다. 둘 다 `reserve` **뒤**다

    앞 판은 router 재시도를 **문 위로** 세어 counted 를 부풀렸다.
    """
    if sdk_max_retries is None:
        sdk_max_retries = sdk_retries_of(contract)
    if slots is None:
        return {"tiers": None, "key_slots": None, "router_tries": None,
                "per_logical_counted": None, "per_logical_raw": None,
                "why": "키 슬롯 수를 못 셌다 — 상한을 지어내지 않는다"}
    tiers = CALL_STRUCTURED_TIERS if contract.get("enable_fallback") else 1
    router_tries = 1 + max(0, int(contract.get("num_retries") or 0))
    sdk_tries = 1 + max(0, int(sdk_max_retries))
    counted = max(1, int(slots)) * tiers          # ★문 **위**만
    below = router_tries * sdk_tries              # ★문 **아래**
    return {
        "tiers": tiers, "key_slots": int(slots), "router_tries": router_tries,
        "litellm_sdk_max_retries": int(sdk_max_retries),
        "per_logical_counted": counted,
        "below_the_door": below,
        "per_logical_raw": counted * below,
        "★above_the_door": "tier × 키 슬롯 — 매번 `reserve` 한다",
        "★counted_below_this": ("Router 의 `num_retries` 와 litellm 이 제 "
                                "client 에 주는 재시도는 `reserve` **아래**라 "
                                "**안 세어진다**"),
    }


def bounds(*, logical: int, contract: Dict[str, Any],
           slots: Optional[int],
           sdk_max_retries: Optional[int] = None) -> Dict[str, Any]:
    """층 셋을 **갈라서** 낸다. ★하나로 합쳐 적으면 거짓이 된다."""
    lay = layers(contract=contract, slots=slots,
                 sdk_max_retries=sdk_max_retries)
    if lay["per_logical_counted"] is None:
        return {"logical": int(logical), "counted": None, "raw_http": None,
                "layers": lay}
    return {
        "logical": int(logical),
        "counted": int(logical) * lay["per_logical_counted"],
        "raw_http": int(logical) * lay["per_logical_raw"],
        "layers": lay,
        "★means": ("`counted` 는 **막을 수 있는** 수다 — 넘으면 네트워크 전에 "
                   "선다. `raw_http` 는 **막을 수 없는** 상한이다"),
    }
