from collections.abc import Container, Sequence, Mapping
from typing import TypeAlias, TypeVar


ParamsFilterSpec: TypeAlias = (
    bool
    | Container[str]
    | tuple[bool, Container[str]]
    | Mapping[str, 'ParamsFilterSpec']
    | tuple[bool, 'ParamsFilterSpec']
)


V = TypeVar('V')


def _split_param_key(key: str) -> tuple[str, ...]:
    return tuple(key.split('__'))


def _match_key_to_filter_spec(
    key: Sequence[str], spec: ParamsFilterSpec, empty_default: bool,
) -> bool:
    if isinstance(spec, Sequence) and (len(spec) == 2) and isinstance(spec[0], bool):
        if (len(key) == 0) and (not spec[0]):
            return empty_default
        spec = spec[1]
    if isinstance(spec, Mapping):
        if len(key) == 0:
            return empty_default
        spec_nested = spec.get(key[0])
        if spec_nested is None:
            return False
        return _whether_to_include_param(key[1:], spec_nested)
    elif isinstance(spec, Container):
        if len(key) == 0:
            return True
        return (key[0] in spec)
    return bool(spec)


def _whether_to_include_param(
    key: Sequence[str], include: ParamsFilterSpec = True, exclude: ParamsFilterSpec = False,
) -> bool:
    return (
        (not _match_key_to_filter_spec(key, exclude, empty_default=False))
        and _match_key_to_filter_spec(key, include, empty_default=True)
    )


def filter_params(
    params: Mapping[str, V],
    include: ParamsFilterSpec = True,
    exclude: ParamsFilterSpec = False,
) -> Mapping[str, V]:
    return {
        k: v
        for k, v in params.items()
        if _whether_to_include_param(_split_param_key(k), include, exclude)
    }
