"""Compatibility exports for the provider model catalog.

Provider value objects live in ``domain`` and catalog entries are discovered
from provider adapters. This module preserves the established public helpers.
"""
from __future__ import annotations

from typing import Optional

from .domain.provider import (
    LeadLaunchSpec,
    ModelAssignment,
    ModelSpec,
    ProviderSpec,
    UnknownModelError,
    UnknownProviderError,
)
from .domain.role import RoleCatalogError, normalize_role
from .model_pool import ModelPool
from .registry.provider_registry import default_provider_registry


_REGISTRY = default_provider_registry()
_MODEL_POOL = ModelPool.from_registry(_REGISTRY)
PROVIDERS = _REGISTRY.providers
CLAUDE = PROVIDERS["claude"].models
ANTIGRAVITY = PROVIDERS["antigravity"].models
CODEX = PROVIDERS["codex"].models
GROK = PROVIDERS["grok"].models
KIMI = PROVIDERS["kimi"].models
PROVIDER_MAPPINGS = {provider: spec.models for provider, spec in PROVIDERS.items()}
ANALYSIS_ROLES = frozenset({"analyser", "critic"})
IMPLEMENTATION_ROLES = frozenset({"executor", "verifier"})

# Legacy role-keyed compatibility defaults. Canonical assignments resolve a
# provider first and then use ProviderSpec.default_models for that role.
ROLE_DEFAULTS = {
    "lead": "opus", "claude": "opus", "codex": "gpt-5.6-sol",
    "antigravity": "gemini-3.1-pro", "report-writer": "sonnet",
}


def lead_launch_spec(provider: str) -> LeadLaunchSpec:
    """The launch spec for a provider that can lead, or an error naming why not."""
    spec = provider_spec(provider)
    if spec.lead_launch is None:
        raise UnknownProviderError(
            f"provider {provider!r} cannot act as a lead: no launch spec"
        )
    return spec.lead_launch


def bare_lead_launch_argv(provider: str) -> list[str]:
    """Start this provider CLI with only the flags required by Okstra."""
    launch = lead_launch_spec(provider)
    return [launch.executable, *launch.sandbox_waiver]


def lead_launch_argv(
    provider: str,
    *,
    model: str,
    prompt: str,
    session_id: str = "",
    waive_sandbox: bool = True,
) -> list[str]:
    """Return the argv that starts one provider CLI as the lead for a run."""
    launch = lead_launch_spec(provider)
    argv = [launch.executable]
    if waive_sandbox:
        argv.extend(launch.sandbox_waiver)
    if model:
        argv.extend([launch.model_flag, model])
    if session_id and launch.start_session_id_flag:
        argv.extend([launch.start_session_id_flag, session_id])
    if launch.prompt_flag:
        argv.append(launch.prompt_flag)
    argv.append(prompt)
    return argv


def provider_spec(provider: str) -> ProviderSpec:
    """Return one registered provider or fail before assignment or dispatch."""
    normalized = (provider or "").strip().lower()
    try:
        return default_provider_registry().resolve(normalized)
    except UnknownProviderError as exc:
        allowed = ", ".join(default_provider_registry().ids())
        raise UnknownProviderError(
            f"unknown provider {provider!r}. Allowed values: {allowed}"
        ) from exc


def provider_ids(role: str | None = None) -> list[str]:
    """Registered provider ids, optionally filtered by functional role."""
    registry = default_provider_registry()
    return list(registry.ids(role if role else None))


def provider_supports_role(provider: str, role: str) -> bool:
    """Whether the provider has passed the registry gate for a role."""
    return provider_spec(provider).supports_role(role)


def provider_wrappers(role: str | None = None) -> dict[str, str]:
    """Provider-to-wrapper mapping derived from capability entries."""
    registry = default_provider_registry()
    return {
        provider: registry.resolve(provider).wrapper
        for provider in registry.ids(role if role else None)
        if registry.resolve(provider).wrapper
    }


def provider_default_model(provider: str, role: str) -> Optional[str]:
    """Resolve a provider explicit default for one functional role."""
    try:
        canonical_role = normalize_role(role)
    except RoleCatalogError:
        return None
    for model in _model_pool().default_candidates(canonical_role):
        if model.provider_id == provider.strip().lower():
            return model.model_ref.model_id
    return None


def resolve_model_metadata(
    *, provider: str, raw_value: str, default_display: str, default_execution: str,
) -> ModelAssignment:
    """Resolve a model alias to its display and execution values."""
    raw_value = (raw_value or "").strip()
    registry = default_provider_registry()
    mapping = registry.providers.get(provider)
    if raw_value and mapping is not None:
        model = _model_pool().resolve_alias(provider, raw_value)
        return ModelAssignment(display=model.display_name, execution=model.execution_value)
    value = raw_value or default_display
    return ModelAssignment(display=value, execution=raw_value or default_execution)


def picker_options(provider: str) -> list[str]:
    """Picker-exposed aliases for a provider, in catalog insertion order."""
    provider_id = provider.strip().lower()
    return [
        model.model_ref.model_id
        for model in _model_pool().list()
        if model.provider_id == provider_id
    ]


def default_model(role: str) -> Optional[str]:
    """Role-to-default model alias; environment overrides belong to the caller."""
    if role in default_provider_registry().providers:
        return provider_default_model(role, "leader")
    try:
        candidates = _model_pool().default_candidates(role)
    except RoleCatalogError:
        return None
    return candidates[0].model_ref.model_id if candidates else None


def catalog_pricing(provider: str) -> dict:
    """Selectable-model pricing as input, cached-input, output USD per million."""
    provider_id = provider.strip().lower()
    return {
        model.model_ref.model_id: model.pricing
        for model in _model_pool().list()
        if model.provider_id == provider_id and model.pricing is not None
    }


def _model_pool() -> ModelPool:
    """Return the pool built from the registry identity cached for this home."""
    registry = default_provider_registry()
    if registry is _REGISTRY:
        return _MODEL_POOL
    return ModelPool.from_registry(registry)
