from __future__ import annotations
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
    pass


def get_nested_dict_value(d: dict[str, Any], keys: list[str]) -> Any:
    """
    Recursively retrieves the value of a nested dictionary.

    Args:
        d (Dict[str, Any]): Dictionary to retrieve the value from.
        keys (List[str]): List of keys corresponding to the path to the value.

    Returns:
        Any: Value of the nested dictionary.

    Raises:
        ValueError: If the input is not a dictionary or the keys list is empty.

    Example:
        get_nested_dict_value(d, ["a", "b", "c"]) is equivalent to d["a"]["b"]["c"].
    """
    if not isinstance(d, dict):
        raise ValueError("Input must be a dictionary.")
    if len(keys) == 0:
        raise ValueError("Keys list must not be empty.")
    elif len(keys) == 1:
        return d[keys[0]]
    else:
        return get_nested_dict_value(d[keys[0]], keys[1:])


def set_nested_dict_value(d: dict[str, Any], keys: list[str], value: Any) -> None:
    """
    Recursively sets the value of a nested dictionary, creating intermediate dictionaries if necessary.

    Args:
        d (Dict[str, Any]): Dictionary to set the value in.
        keys (List[str]): List of keys corresponding to the path to the value.
        value (Any): Value to set.

    Raises:
        ValueError: If the input is not a dictionary or the keys list is empty.

    Example:
        set_nested_dict_value(d, ["a", "b", "c"], 1) is equivalent to d["a"]["b"]["c"] = 1.
    """
    if not isinstance(d, dict):
        raise ValueError("Input must be a dictionary.")
    if len(keys) == 0:
        raise ValueError("Keys list must not be empty.")
    elif len(keys) == 1:
        d[keys[0]] = value
    else:
        if keys[0] not in d:
            d[keys[0]] = {}
        set_nested_dict_value(d[keys[0]], keys[1:], value)


def get_nested_attr(obj: Any, attrs: list[str]) -> Any:
    """
    Recursively retrieves the value of a nested attribute in an object.

    Args:
        obj (Any): Object to retrieve the attribute from.
        attrs (list[str]): List of attribute names corresponding to the path to the attribute.

    Returns:
        Any: The value of the nested attribute.

    Raises:
        ValueError: If the attribute list is empty.
        AttributeError: If any intermediate attribute does not exist.

    Example:
        get_nested_attr(obj, ["a", "b", "c"]) is equivalent to obj.a.b.c.
    """
    if len(attrs) == 0:
        raise ValueError("Attribute list must not be empty.")
    elif len(attrs) == 1:
        return getattr(obj, attrs[0])
    else:
        return get_nested_attr(getattr(obj, attrs[0]), attrs[1:])



def set_nested_attr(obj: Any, attrs: list[str], value: Any) -> None:
    """
    Recursively sets the value of a nested attribute in an object, creating intermediate objects if necessary.

    Args:
        obj (Any): Object to set the attribute on.
        attrs (list[str]): List of attribute names corresponding to the path to the attribute.
        value (Any): Value to set.

    Raises:
        ValueError: If the attribute list is empty.

    Example:
        set_nested_attr(obj, ["a", "b", "c"], 1) is equivalent to obj.a.b.c = 1.
    """
    if len(attrs) == 0:
        raise ValueError("Attribute list must not be empty.")
    elif len(attrs) == 1:
        setattr(obj, attrs[0], value)
    else:
        if not hasattr(obj, attrs[0]):
            setattr(obj, attrs[0], type(attrs[0], (object,), {})())  # Create intermediate object
        set_nested_attr(getattr(obj, attrs[0]), attrs[1:], value)
