from collections.abc import Callable
from typing import TypeVar, overload

from remedapy.decorator import make_data_last

Key = TypeVar('Key', str, int)
T = TypeVar('T')


@overload
def merge_deep(destination: dict[Key, T], source: dict[Key, T], /) -> dict[Key, T]: ...


@overload
def merge_deep(source: dict[Key, T], /) -> Callable[[dict[Key, T]], dict[Key, T]]: ...


@make_data_last
def merge_deep(destination: dict[Key, T], source: dict[Key, T], /) -> dict[Key, T]:
    """
    Merges two dicts recursively.

    Parameters
    ----------
    destination : dict[Key, T]
        The destination dict (positional-only).
    source : dict[Key, T]
        The source dict (positional-only).

    Returns
    -------
    dict[K, T]
        The resulting dict.

    See Also
    --------
    merge
    merge_all

    Examples
    --------
    Data first:
    >>> R.merge_deep({'foo': 'bar', 'x': 1}, {'foo': 'baz', 'y': 2})
    {'foo': 'baz', 'x': 1, 'y': 2}
    >>> R.merge_deep({'x': 1, 'y': {'a': 3}}, {'y': {'b': 3}, 'z': 2})
    {'x': 1, 'y': {'a': 3, 'b': 3}, 'z': 2}

    Data last:
    >>> R.pipe({'foo': 'bar', 'x': 1}, R.merge_deep({'foo': 'baz', 'y': 2}))
    {'foo': 'baz', 'x': 1, 'y': 2}

    """
    result: dict[Key, T] = {}
    for k, v in destination.items():
        if k not in source.keys():
            result[k] = v
        else:
            if not isinstance(v, dict) or not isinstance(source[k], dict):
                result[k] = source[k]
            else:
                result[k] = merge_deep(v, source[k])  # pyright: ignore[reportArgumentType, reportUnknownArgumentType]
    for k, v in source.items():
        if k in destination.keys():
            continue
        result[k] = v
    return result
