"""provider 注册表(D11): 消灭 `"qwen" in provider` 式字符串猜测。

每个 provider 显式声明 thinking 参数注入形态与响应处理差异;查找按名字
**精确匹配**,未注册即装配期报错。注册是纯函数——返回新表,不修改共享
状态(纯 asyncio 中立铁律);client 经 `registry` 参数持有自己的表。

**本模块只存放声明,不做判断**: 拿这些声明去决定注入什么、响应算不算推理,
全部在 `thinking.py`(P7 决策逻辑与状态存储分离)。
"""

from collections.abc import Mapping
from dataclasses import dataclass
from types import MappingProxyType
from typing import Any


@dataclass(frozen=True)
class ThinkingWire:
    """一个 provider 表达"开/关/多深"的请求体形态(设计 §3.3)。

    三个字段各自的 `None` **语义互不重叠**,混淆任意两个都会退回 issue #5 修掉的
    那种静默失效:

    ==============  ==========================================================
    ``on_base=None``  **形态未知**: 本库不知道该 provider 如何表达"开",配了开关
                      即装配期报错并指路 `register_provider`/`extra_body`
    ``off=None``      已知开启形态,但**没有关闭形态**(该 provider 关不掉)
    ``effort_key``    ``None`` = 该 provider 只有开关、没有档位(qwen 系靠
      ``=None``       ``thinking_budget`` 调深度,不是档位)
    ==============  ==========================================================

    `on_base={}` 与 `on_base=None` 同样不可混: 前者是"已知无需注入任何参数即处于
    开启档"(经网关的 OpenAI 兼容路径正是如此——档位由 `effort_key` 单独附加),
    后者是"不知道怎么表达"。

    **为什么不是 cherry-studio 那套 wire DSL**: 它要支持 openai-chat /
    openai-responses / anthropic-messages / google-generate-content 四种端点协议,
    故需要 closed operation 集合与 `budgetWire` 代际变体。本库只有一个 OpenAI 兼容
    transport,跨协议转换由 new-api 在服务端完成(它自己就有一层 canonical intent),
    一个协议一层形态即够(P1 YAGNI)。
    """

    off: Mapping[str, Any] | None
    on_base: Mapping[str, Any] | None
    effort_key: str | None


@dataclass(frozen=True)
class ProviderProfile:
    """单个 provider 的能力与差异声明。

    `thinking` 声明推理参数的**形态**(按 provider 变,数年不变一次);
    `strip_think_tags` 声明响应 content 需剥离 ``<think>`` 标签(qwen 系);
    `supports_native_schema` 供 D14 阶梯选择原生 response_format。

    注: 本类只声明**形态**(参数长什么样,按 provider 变);某个具体模型支持哪些
    档位属**能力**(按 model 变),见 `thinking.ThinkingCapability`。二者合一在
    provider 级表达不了代际差异——glm-5.2 能关而 glm-5.3 不能,形态却完全相同。
    """

    name: str
    thinking: ThinkingWire
    strip_think_tags: bool
    supports_native_schema: bool = False


# 经 new-api 中转的口径。四家参考实现(cherry-studio / OpenRouter / LiteLLM /
# new-api 自身)一致的结论: OpenAI 兼容端点上,档位一律走标准的 `reasoning_effort`,
# 跨协议转换(→ Claude 的 thinking、Gemini 的 thinkingConfig)由网关服务端完成。
DEFAULT_PROFILES: Mapping[str, ProviderProfile] = MappingProxyType(
    {
        # 注入片段出处: VT llm.py:130-144(开启形态)与 CHS invokers.py:230-238(关闭形态)
        "qwen": ProviderProfile(
            name="qwen",
            thinking=ThinkingWire(
                off={"enable_thinking": False},
                on_base={"enable_thinking": True},
                # 百炼的深度控制是 `thinking_budget`(token 预算)而非档位;
                # 预算型控制本库当前不支持(设计 §11 明确不做)
                effort_key=None,
            ),
            strip_think_tags=True,
        ),
        # 官方 thinking_mode 文档: thinking:{type} 是开关,reasoning_effort 是深度,
        # V4 一代两者并用(deepseek-v4-* 的档位见能力表)
        "deepseek": ProviderProfile(
            name="deepseek",
            thinking=ThinkingWire(
                off={"thinking": {"type": "disabled"}},
                on_base={"thinking": {"type": "enabled"}},
                effort_key="reasoning_effort",
            ),
            strip_think_tags=False,
        ),
        # issue #20。智谱官方迁移建议原文: 原先用 {"type":"disabled"} 的应改为
        # {"type":"enabled"} + reasoning_effort="low"——GLM-5.3 起 thinking.type
        # 不再接受 disabled,故"关"这一档由能力表按型号裁定(5.2 能关,5.3 不能)
        "zhipu": ProviderProfile(
            name="zhipu",
            thinking=ThinkingWire(
                off={"thinking": {"type": "disabled"}},
                on_base={"thinking": {"type": "enabled"}},
                effort_key="reasoning_effort",
            ),
            strip_think_tags=False,
        ),
        # kimi-k3 的档位是 low/high/max;thinking.type 为月之暗面的开关形态
        "moonshot": ProviderProfile(
            name="moonshot",
            thinking=ThinkingWire(
                off={"thinking": {"type": "disabled"}},
                on_base={"thinking": {"type": "enabled"}},
                effort_key="reasoning_effort",
            ),
            strip_think_tags=False,
        ),
        # 2026-08-02 经 new-api 中转实测(findings §2),2026-08-25 复测结论不变。
        # enable_thinking / thinking 两种写法均被静默丢弃(prompt_tokens 恒等于基线
        # 194),reasoning_effort 才是真开关——本段形态据此成立。
        # `on_base={}`: "开"本身不需要任何参数,档位(若模型支持)由 effort_key 附加。
        # 旧版此处硬编码 {"reasoning_effort": "medium"},那是替下游做的档位判断,
        # 且 medium 在 GLM/kimi/deepseek 的档位表里根本不存在——正是本次要消灭的
        "minimax": ProviderProfile(
            name="minimax",
            thinking=ThinkingWire(
                off={"reasoning_effort": "none"}, on_base={}, effort_key="reasoning_effort"
            ),
            strip_think_tags=False,
        ),
        # OpenAI 兼容基线段名: 实践中被复用为**任意**兼容厂商的兜底。两档此前标
        # None(未知),因为当时无法区分"厂商方言"与"标准字段";`reasoning_effort`
        # 是 OpenAI **官方**字段而非方言,发给经网关的兼容端点不会打到不认识它的
        # 厂商,故 2026-09-04 起给出标准形态。真正形态未知的 provider 仍走
        # register_provider 注册,而不是挂在本段下
        "openai": ProviderProfile(
            name="openai",
            thinking=ThinkingWire(
                off={"reasoning_effort": "none"}, on_base={}, effort_key="reasoning_effort"
            ),
            strip_think_tags=False,
        ),
        # Claude 5 系原生是 thinking.type=adaptive + output_config.effort,Gemini 3 系
        # 原生是 thinkingConfig.thinkingLevel;两者的代际方言(Claude ≤4.5 的
        # budget_tokens、Gemini 2.x 的 thinkingBudget)由 new-api 的 canonical intent
        # 层吸收,本库只发 OpenAI 形态(设计 §3.4)
        "anthropic": ProviderProfile(
            name="anthropic",
            thinking=ThinkingWire(
                off={"reasoning_effort": "none"}, on_base={}, effort_key="reasoning_effort"
            ),
            strip_think_tags=False,
        ),
        "google": ProviderProfile(
            name="google",
            thinking=ThinkingWire(
                off={"reasoning_effort": "none"}, on_base={}, effort_key="reasoning_effort"
            ),
            strip_think_tags=False,
        ),
    }
)


def get_provider(
    name: str, *, registry: Mapping[str, ProviderProfile] | None = None
) -> ProviderProfile:
    """按名字精确查找 profile;未注册直接报错(严禁默认值掩盖配置错误)。"""
    table = DEFAULT_PROFILES if registry is None else registry
    profile = table.get(name)
    if profile is None:
        raise ValueError(
            f"未注册的 provider: {name!r}(已注册: {sorted(table)});"
            f"新 provider 用 register_provider(ProviderProfile(...)) 注册后经 registry 参数传入"
        )
    return profile


def register_provider(
    profile: ProviderProfile, *, base: Mapping[str, ProviderProfile] | None = None
) -> dict[str, ProviderProfile]:
    """纯函数注册: 返回 base(缺省 DEFAULT_PROFILES)+ 新条目的新表,同名覆盖。"""
    table = dict(DEFAULT_PROFILES if base is None else base)
    table[profile.name] = profile
    return table
