from __future__ import annotations
from typing import TYPE_CHECKING, List, Tuple, Dict, Optional, Union, Any
if TYPE_CHECKING:
    pass

import os

import yaml


def load_yaml_config(config_path: str) -> Dict[str, Any]:
    """
    Loads configuration from YAML file.
    """
    if not os.path.exists(config_path):
        raise FileNotFoundError(f"Configuration file not found: {config_path}")
    try:
        with open(config_path, "r") as file:
            return yaml.safe_load(file)
    except yaml.YAMLError as e:
        raise ValueError(f"Error parsing configuration file: {e}")
    except Exception as e:
        raise ValueError(f"Error loading configuration file: {e}")


def save_yaml_config(config: Dict[str, Any], config_path: str) -> None:
    """
    Saves configuration to YAML file.
    """
    with open(config_path, "w") as file:
        yaml.dump(config, file)


def get_nested_dict_value(d: Dict[str, Any], keys: List[str]) -> Any:
    """
    Recursively retrieves the value of a nested dictionary given a list of keys corresponding to the path to the value.
    """
    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 given a list of keys corresponding to the path to the value.
    """
    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:
        set_nested_dict_value(d[keys[0]], keys[1:], value)
