"""Shared utilities for LLM provider construction.

These helpers were previously inlined in ``config/llm_config.get_llm``'s
procedural ``if/elif`` chain (which is now removed). They live here so
provider-class ``create_llm`` methods are the single source of truth for
LLM construction logic — no per-provider drift.
"""

DEFAULT_LOCAL_CONTEXT_WINDOW_SIZE = 8192
DEFAULT_CLOUD_CONTEXT_WINDOW_SIZE = 128000
LOCAL_PROVIDERS = ("ollama", "llamacpp", "lmstudio")


def get_context_window_for_provider(provider_type, settings_snapshot=None):
    """Resolve effective context window size for a provider.

    Local providers (ollama, llamacpp, lmstudio) use a smaller default to
    prevent memory issues. Cloud providers respect
    ``llm.context_window_unrestricted`` and return ``None`` when
    unrestricted (the provider auto-handles its own context window).
    """
    from ...config.thread_settings import get_setting_from_snapshot

    if provider_type in LOCAL_PROVIDERS:
        window_size = get_setting_from_snapshot(
            "llm.local_context_window_size",
            DEFAULT_LOCAL_CONTEXT_WINDOW_SIZE,
            settings_snapshot=settings_snapshot,
        )
        return (
            int(window_size)
            if window_size is not None
            else DEFAULT_LOCAL_CONTEXT_WINDOW_SIZE
        )
    use_unrestricted = get_setting_from_snapshot(
        "llm.context_window_unrestricted",
        True,
        settings_snapshot=settings_snapshot,
    )
    if use_unrestricted:
        return None
    window_size = get_setting_from_snapshot(
        "llm.context_window_size",
        DEFAULT_CLOUD_CONTEXT_WINDOW_SIZE,
        settings_snapshot=settings_snapshot,
    )
    return (
        int(window_size)
        if window_size is not None
        else DEFAULT_CLOUD_CONTEXT_WINDOW_SIZE
    )


def compute_max_tokens(
    settings_snapshot=None, context_window_size=None
) -> int | None:
    """Resolve effective max_tokens for ``ChatXxx(max_tokens=...)``.

    Caps at 80% of ``context_window_size`` (when provided) to leave room
    for the prompt. Returns ``None`` (caller should omit the kwarg) when:

    - ``llm.supports_max_tokens`` is False, OR
    - ``llm.max_tokens`` is unset / explicitly None in the snapshot.

    Omitting the kwarg when the setting is absent matches the pre-refactor
    live-class behavior (the provider SDK's own default applies); a
    hardcoded fallback like the dead chain's 100000 exceeds the output
    limit of most cloud models. Production users have ``llm.max_tokens``
    populated from ``default_settings.json`` (currently 30000), so the
    unset branch only fires for partial-snapshot programmatic callers.

    Raises:
        NoSettingsContextError: when ``llm.max_tokens`` is absent from the
            provided snapshot (or no snapshot is given) and no thread
            settings context is available. A snapshot being present does
            NOT prevent the raise — callers must wrap construction in
            ``except NoSettingsContextError`` and omit the kwarg.
    """
    from ...config.thread_settings import get_setting_from_snapshot

    if not get_setting_from_snapshot(
        "llm.supports_max_tokens",
        True,
        settings_snapshot=settings_snapshot,
    ):
        return None
    raw = get_setting_from_snapshot(
        "llm.max_tokens",
        None,
        settings_snapshot=settings_snapshot,
    )
    if raw is None:
        return None
    max_tokens = int(raw)
    if context_window_size is not None:
        max_tokens = min(max_tokens, int(context_window_size * 0.8))
    return max_tokens
