"""Helpers for runtime model preset selection."""

from __future__ import annotations

from collections.abc import Callable, Mapping
from dataclasses import replace
from pathlib import Path

from nanobot.config.schema import Config, ModelPresetConfig
from nanobot.providers.base import LLMProvider
from nanobot.providers.factory import ProviderSnapshot, build_provider_snapshot

PresetSnapshotLoader = Callable[[str], ProviderSnapshot]
PresetCatalogLoader = Callable[[], Mapping[str, ModelPresetConfig]]


def default_selection_signature(
    signature: tuple[object, ...] | None,
    model_preset: str | None = None,
) -> tuple[object, ...] | None:
    return (model_preset, *signature[:2]) if signature else None


def configured_model_presets(config: Config) -> dict[str, ModelPresetConfig]:
    return {**config.model_presets, "default": config.resolve_default_preset()}


def load_model_preset_catalog(
    config_path: Path | None = None,
) -> dict[str, ModelPresetConfig]:
    """Load the current preset catalog from the configured file."""
    from nanobot.config.loader import load_config, resolve_config_env_vars

    return configured_model_presets(
        resolve_config_env_vars(
            load_config(config_path),
            config_path=config_path,
        ),
    )


def make_preset_snapshot_loader(
    config: Config,
    provider_snapshot_loader: Callable[..., ProviderSnapshot] | None,
) -> PresetSnapshotLoader:
    if provider_snapshot_loader is not None:
        return lambda name: provider_snapshot_loader(preset_name=name)
    return lambda name: build_provider_snapshot(config, preset_name=name)


def build_static_preset_snapshot(
    provider: LLMProvider,
    name: str,
    preset: ModelPresetConfig,
) -> ProviderSnapshot:
    return ProviderSnapshot(
        provider=provider,
        model=preset.model,
        context_window_tokens=preset.context_window_tokens,
        signature=("model_preset", name, preset.model_dump_json()),
        generation=preset.to_generation_settings(),
        model_preset=name,
    )


def build_runtime_preset_snapshot(
    *,
    name: str,
    presets: dict[str, ModelPresetConfig],
    provider: LLMProvider,
    loader: PresetSnapshotLoader | None,
) -> ProviderSnapshot:
    if loader is not None:
        return replace(loader(name), model_preset=name)
    return build_static_preset_snapshot(provider, name, presets[name])


def normalize_preset_name(name: str | None, presets: dict[str, ModelPresetConfig]) -> str:
    if not isinstance(name, str) or not name.strip():
        raise ValueError("model_preset must be a non-empty string")
    name = name.strip()
    if name in presets:
        return name
    matches = [candidate for candidate in presets if candidate.casefold() == name.casefold()]
    if len(matches) == 1:
        return matches[0]
    raise KeyError(f"model_preset {name!r} not found. Available: {', '.join(presets) or '(none)'}")
