import typing as t

from miska import missing


def new(data: dict, *exclude: t.Hashable, **updates: t.Any) -> dict:
    new_data = wipe(data, keys=exclude, copy=True)
    new_data.update(updates)
    return new_data


def wipe(
    data: dict,
    *,
    key: t.Hashable | missing.MISSING = missing.MISSING(),
    keys: t.Iterable[t.Hashable] | None = None,
    copy: bool = False,
) -> dict:
    if copy:
        data = data.copy()
    if missing.is_missing(key):
        data.pop(keys, None)
    for k in keys or []:
        data.pop(k, None)
    return data


def move(
    data: dict,
    *,
    key: t.Hashable,
    new_key: t.Hashable,
    copy: bool = False,
) -> dict:
    if copy:
        data = data.copy()
    value = data.pop(key)
    data[new_key] = value
    return data


def rekey(
    data: dict,
    *,
    func: t.Callable[[t.Hashable], t.Hashable],
    keys: t.Iterable[t.Hashable] | None = None,
    copy: bool = False,
) -> dict:
    if copy:
        data = data.copy()
    for k in keys or data.keys():
        data[func(k)] = data.pop(k)
    return data


def revalue(
    data: dict,
    *,
    keys: t.Iterable[t.Hashable] | None = None,
    func: t.Callable[[t.Any], t.Any],
    copy: bool = False,
) -> dict:
    if copy:
        data = data.copy()
    for k in keys or data.keys():
        data[k] = func(data[k])
    return data


def dict_by(
    it: t.Iterable[t.Any],
    *,
    key: t.Hashable | t.Callable[[t.Any], t.Hashable],
) -> dict[t.Hashable, t.Any]:
    if not callable(key):
        key = lambda item: item[key]
    return {key(item): item for item in it}
