from collections.abc import Callable, Mapping
from typing import Any, TypeAlias, TypeVar, overload

import remedapy as R
from remedapy.decorator import make_data_last

Key = TypeVar('Key', str, int)
T = TypeVar('T')
Evolver: TypeAlias = Mapping[Key, 'Callable[[Any], Any] | Evolver[Key]']


@overload
def evolve(data: dict[Key, Any], evolver: Evolver[Key], /) -> dict[Key, Any]: ...


@overload
def evolve(evolver: Evolver[Key], /) -> Callable[[dict[Key, Any]], dict[Key, Any]]: ...


@make_data_last
def evolve(data: dict[Key, Any], evolver: Evolver[Key], /) -> dict[Key, Any]:
    """
    Creates a new dict by applying functions from evolver to the values from the given dict.

    Parameters
    ----------
    data: dict[K, V]
        The dict to apply the evolver to.
    evolver: Evolver[K]
        The evolver to apply to the values from the given dict.

    Returns
    -------
    dict[K, V]
        The new dict with the same keys as the given dict and the values transformed by the evolver.

    Examples
    --------
    Data first:
    >>> evolver = {
    ...     'count': R.add(1),
    ...     'time': {'elapsed': R.add(1), 'remaining': R.add(-1)},
    ... }
    >>> data = {
    ...     'id': 10,
    ...     'count': 10,
    ...     'time': {'elapsed': 100, 'remaining': 1400},
    ... }
    >>> R.evolve(data, evolver)
    {'id': 10, 'count': 11, 'time': {'elapsed': 101, 'remaining': 1399}}

    Data last:
    >>> R.evolve(evolver)(data)
    {'id': 10, 'count': 11, 'time': {'elapsed': 101, 'remaining': 1399}}

    """
    result: dict[Key, Any] = {}
    for k, v in data.items():
        if k in evolver:
            if isinstance(evolver[k], dict):
                result[k] = evolve(v, evolver[k])  # pyright: ignore[reportArgumentType]
            elif callable(evolver[k]):
                result[k] = R.apply(v, evolver[k])  # pyright: ignore[reportArgumentType]
            else:  # pragma: no cover - should not happen
                raise TypeError(f'Evolver for key {k} must be a function or a dict')
        else:
            result[k] = v
    return result
