# src/pytools/core/get_from_obj.py
from typing import Type, TypeVar, Optional, Any, Mapping

T = TypeVar("T")

def get_from_obj(
    obj: Optional[Mapping[str, Any]] = None,
    key: str = "",
    typ: Type[T] = None,
    **kwargs
) -> T:
    """
    Fetch a value from a context-like object or kwargs and ensure it is of the expected type.

    Args:
        ctx: A context-like dictionary (e.g., typer.Context.obj).
        key: The key to fetch from obj or kwargs.
        typ: Expected type of the value (class/type).
        **kwargs: Optional fallback if obj is None or key not present.

    Returns:
        The value cast to type `typ`.

    Raises:
        KeyError: If the key is not found.
        TypeError: If the value cannot be cast to the expected type.
    """
    value: Any = None

    # 1. Try ctx first
    if obj and key in obj:
        value = obj[key]

    # 2. Fallback to kwargs
    elif key in kwargs:
        value = kwargs[key]

    # 3. If still None, error
    if value is None:
        raise KeyError(f"Key '{key}' not found in context or kwargs")

    # 4. Type check / cast
    if typ and not isinstance(value, typ):
        try:
            value = typ(value)
        except Exception as e:
            raise TypeError(f"Failed to cast key '{key}' value to type {typ}: {e}")

    return value
