"""Model profile types and utilities."""

import logging
import warnings
from typing import get_type_hints

from pydantic import ConfigDict
from typing_extensions import TypedDict

logger = logging.getLogger(__name__)


class ModelProfile(TypedDict, total=False):
    """Description of a chat model's capabilities, exposed via `model.profile`.

    See the
    [model profiles guide](https://docs.langchain.com/oss/python/langchain/models#model-profiles)
    for concepts and usage. Data is sourced from
    [models.dev](https://github.com/sst/models.dev), augmented with additional
    fields, and generated by the
    [`langchain-model-profiles`](https://github.com/langchain-ai/langchain/tree/master/libs/model-profiles)
    package (via its `langchain-profiles` CLI).

    !!! warning "Beta feature"

        Fields and format are subject to change. This is a `total=False`
        `TypedDict`, so any field may be absent — guard accesses with `.get()`.
    """

    __pydantic_config__ = ConfigDict(extra="allow")  # type: ignore[misc]

    # --- Model metadata ---

    name: str
    """Human-readable model name (e.g., `'GPT-5'`)."""

    status: str
    """Model lifecycle status (e.g., `'active'`, `'deprecated'`)."""

    release_date: str
    """Model release date (ISO 8601 format, e.g., `'2025-06-01'`)."""

    last_updated: str
    """Date the model was last updated (ISO 8601 format)."""

    open_weights: bool
    """Whether the model weights are openly available."""

    # --- Input constraints ---

    max_input_tokens: int
    """Maximum context window (tokens)."""

    text_inputs: bool
    """Whether text inputs are supported."""

    image_inputs: bool
    """Whether image inputs are supported."""
    # TODO: add more detail about formats?

    image_url_inputs: bool
    """Whether [image URL inputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""

    pdf_inputs: bool
    """Whether [PDF inputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""
    # TODO: add more detail about formats? e.g. bytes or base64

    audio_inputs: bool
    """Whether [audio inputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""
    # TODO: add more detail about formats? e.g. bytes or base64

    video_inputs: bool
    """Whether [video inputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""
    # TODO: add more detail about formats? e.g. bytes or base64

    image_tool_message: bool
    """Whether images can be included in `ToolMessage` content."""

    pdf_tool_message: bool
    """Whether PDFs can be included in `ToolMessage` content."""

    # --- Output constraints ---

    max_output_tokens: int
    """Maximum output tokens."""

    reasoning_output: bool
    """Whether the model supports [reasoning / chain-of-thought](https://docs.langchain.com/oss/python/langchain/models#reasoning)."""

    reasoning_effort_levels: list[str]
    """Supported reasoning-effort levels (e.g. `['low', 'medium', 'high']`).

    Absent or empty if the model does not support a configurable reasoning
    effort. Only meaningful when `reasoning_output` is `True`.
    """

    reasoning_effort_default: str
    """The provider's documented default reasoning-effort level, if known.

    Absent when no default is documented. Only meaningful when
    `reasoning_effort_levels` is non-empty; not necessarily a member of
    `reasoning_effort_levels` itself (a model may default to unconfigurable
    behavior distinct from any explicit level).
    """

    text_outputs: bool
    """Whether text outputs are supported."""

    image_outputs: bool
    """Whether [image outputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""

    audio_outputs: bool
    """Whether [audio outputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""

    video_outputs: bool
    """Whether [video outputs](https://docs.langchain.com/oss/python/langchain/models#multimodal)
    are supported."""

    # --- Tool calling ---
    tool_calling: bool
    """Whether the model supports [tool calling](https://docs.langchain.com/oss/python/langchain/models#tool-calling)."""

    tool_choice: bool
    """Whether the model supports [tool choice](https://docs.langchain.com/oss/python/langchain/models#forcing-tool-calls)."""

    tool_call_streaming: bool
    """Whether the model returns properly structured `tool_call_chunks` when streaming.

    Only meaningful when `tool_calling` is `True`.
    """

    # --- Structured output ---
    structured_output: bool
    """Whether the model supports native [structured output](https://docs.langchain.com/oss/python/langchain/models#structured-outputs)."""

    # --- Other capabilities ---

    attachment: bool
    """Whether the model supports file attachments."""

    temperature: bool
    """Whether the model supports a temperature parameter."""


ModelProfileRegistry = dict[str, ModelProfile]
"""Registry mapping model identifiers or names to their ModelProfile."""


def _warn_unknown_profile_keys(profile: ModelProfile) -> None:
    """Warn if `profile` contains keys not declared on `ModelProfile`.

    Args:
        profile: The model profile dict to check for undeclared keys.
    """
    if not isinstance(profile, dict):
        return  # type: ignore[unreachable]

    try:
        declared = frozenset(get_type_hints(ModelProfile).keys())
    except (TypeError, NameError):
        # get_type_hints raises NameError on unresolvable forward refs and
        # TypeError when annotations evaluate to non-type objects.
        logger.debug(
            "Could not resolve type hints for ModelProfile; "
            "skipping unknown-key check.",
            exc_info=True,
        )
        return

    extra = sorted(set(profile) - declared)
    if extra:
        warnings.warn(
            f"Unrecognized keys in model profile: {extra}. "
            f"This may indicate a version mismatch between langchain-core "
            f"and your provider package. Consider upgrading langchain-core.",
            stacklevel=2,
        )
