"""MLX single-inference 전역 gate (Phase 3.1.1).

Mac mini MLX primary(gemma-4-26b-a4b-it-8bit)는 **single-inference**다.
동시 호출이 들어오면 queue가 폭발한다(실측: 23 concurrent 요청 → 22개 15초 timeout).

이 모듈은 analyzer / evidence / synthesis 등 **모든 MLX-bound LLM 호출**이
공유하는 `asyncio.Semaphore(1)`를 제공한다. MLX를 호출하는 경로는 예외 없이
`async with get_mlx_gate():` 블록 안에서만 `AIClient._call_chat(ai.primary, ...)`
를 호출해야 한다.

## 영구 룰

- **MLX primary 호출 경로는 예외 없이 gate 획득 필수**. query_analyzer /
  evidence_service / synthesis_service 세 곳이 현재 사용자. 이후 경로가 늘어도
  동일 gate를 import해서 사용한다. 새 Semaphore를 만들지 말 것 (큐 분할 시
  동시 실행 발생).
- **`asyncio.timeout(...)`은 gate 안쪽에서만 적용**. gate 대기 자체에 timeout을
  걸면 "대기만으로 timeout 발동" 버그가 재발한다(query_analyzer 초기 이슈).
- **fallback(Ollama) 경로는 gate 제외**. GPU Ollama는 concurrent OK. 단 현재
  구현상 `AIClient._call_chat` 내부에서 primary→fallback 전환이 일어나므로
  fallback도 gate 점유 상태로 실행된다. 허용 가능(fallback 빈도 낮음).
- **MLX concurrency는 `MLX_CONCURRENCY = 1` 고정**. 모델이 바뀌어도 single-
  inference 특성이 깨지지 않는 한 이 값을 올리지 말 것.

## 확장 여지 (지금은 구현하지 않음)

트래픽 증가 시 "우선순위 역전"(/ask가 analyzer background task 뒤에 밀림)이
문제가 되면 `asyncio.PriorityQueue` 기반 우선순위 큐로 교체 가능. Gate 자체
분리(get_analyzer_gate / get_ask_gate)는 single-inference에서 throughput
개선이 없으므로 의미 없음.
"""

from __future__ import annotations

import asyncio

# MLX primary는 single-inference → 1
MLX_CONCURRENCY = 1

# 첫 호출 시 현재 event loop에 바인딩된 Semaphore 생성 (lazy init)
_mlx_gate: asyncio.Semaphore | None = None


def get_mlx_gate() -> asyncio.Semaphore:
    """MLX primary 호출 경로 공용 gate. 최초 호출 시 lazy init.

    사용 예:
        async with get_mlx_gate():
            async with asyncio.timeout(LLM_TIMEOUT_MS / 1000):
                raw = await ai_client._call_chat(ai_client.ai.primary, prompt)

    ⚠ `asyncio.timeout`은 반드시 gate 안쪽에 둘 것. 바깥에 두면 gate 대기만으로
    timeout이 발동한다.
    """
    global _mlx_gate
    if _mlx_gate is None:
        _mlx_gate = asyncio.Semaphore(MLX_CONCURRENCY)
    return _mlx_gate
