"""Extract base and container types from annotations (unions, generics)."""

from typing import Union, get_origin, get_args
from types import UnionType, GenericAlias


def get_base_type(complex_type: type) -> tuple[type, tuple, bool]:
    """
    Extracts the base type from a given complex type and determines if it is optional.

    This function handles both simple types and union types (e.g., types that
    can be None). For container types like `list[str]` or `dict[str, list[int]]`,
    it extracts the base container type.
    It returns a tuple where the first element is the base type and the second element is a boolean
    indicating whether the type is optional (i.e., can be None).

    Args:
        complex_type (type): The complex type to be analyzed. This can be a
            simple type, a container type, or a union type with None.

    Returns:
        tuple[type, bool]: A tuple containing the base type and a boolean
            indicating if the type is optional. For example:
                          - For `Something`, it returns `(Something, True)`.
                          - For `Something | None`, it returns `(Something, False)`.
                          - For `list[str]`, it returns `(list, True)`.
                          - For `dict[str, list[int]] | None`, it returns `(dict, False)`.

    Raises:
        TypeError: If the complex type is a union that does not exclusively
            involve a single type and None.
    """

    origin = get_origin(complex_type)
    args = get_args(complex_type)

    # Check if it's a union type, including the new | syntax
    if origin is Union or origin is UnionType:
        if complex_type in (
            type | GenericAlias,
            type | GenericAlias | None,
            type | GenericAlias | UnionType,
            type | GenericAlias | UnionType | None,
        ):
            return type, (), None in args
        if type(None) not in args:
            raise TypeError(
                f"Complex type must be a union with None only. This is {complex_type}"
            )
        # Filter out None type
        base_type = next(arg for arg in args if arg is not type(None))
        return (get_container_base_type(base_type), get_args(base_type), False)
    return (get_container_base_type(complex_type), get_args(complex_type), True)


def get_container_base_type(type_: type) -> type:
    """
    Extracts the base container type from a given type.

    This helper function is used to get the base type of container types.
    For example, it extracts `list` from `list[str]` and `dict` from
    `dict[str, list[int]]`.
    If the input type is not a container type, it returns the type as-is.

    Args:
        type_ (type): The type to be analyzed. This can be a simple type or a container type.

    Returns:
        type: The base container type if the input is a container type;
            otherwise, the input type itself. For example:
             - For `list[str]`, it returns `list`.
             - For `dict[str, list[int]]`, it returns `dict`.
             - For `int`, it returns `int`.
    """
    origin = get_origin(type_)
    if origin is not None:
        return origin
    return type_
