from __future__ import annotations

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

from .list import FSharpList
from .map_util import add_to_dict, add_to_set, get_item_from_dict, try_get_value
from .mutable_map import Dictionary
from .mutable_set import HashSet
from .seq import delay, filter, map, to_array, to_list
from .types import Array, FSharpRef
from .util import IEnumerable_1, IEqualityComparer_1, get_enumerator


_T = TypeVar("_T")

_KEY = TypeVar("_KEY")


def distinct(xs: IEnumerable_1[Any], comparer: IEqualityComparer_1[Any]) -> IEnumerable_1[Any]:
    def _arrow85(__unit: None = None, xs: Any = xs, comparer: Any = comparer) -> IEnumerable_1[_T]:
        hash_set: Any = HashSet([], comparer)

        def predicate(x: _T | None = None) -> bool:
            return add_to_set(x, hash_set)

        return filter(predicate, xs)

    return delay(_arrow85)


def distinct_by(
    projection: Callable[[_T], _KEY], xs: IEnumerable_1[Any], comparer: IEqualityComparer_1[Any]
) -> IEnumerable_1[Any]:
    def _arrow86(
        __unit: None = None, projection: Any = projection, xs: Any = xs, comparer: Any = comparer
    ) -> IEnumerable_1[_T]:
        hash_set: Any = HashSet([], comparer)

        def predicate(x: _T | None = None) -> bool:
            return add_to_set(projection(x), hash_set)

        return filter(predicate, xs)

    return delay(_arrow86)


def except_(
    items_to_exclude: IEnumerable_1[Any], xs: IEnumerable_1[Any], comparer: IEqualityComparer_1[Any]
) -> IEnumerable_1[Any]:
    def _arrow87(
        __unit: None = None, items_to_exclude: Any = items_to_exclude, xs: Any = xs, comparer: Any = comparer
    ) -> IEnumerable_1[_T]:
        hash_set: Any = HashSet(items_to_exclude, comparer)

        def predicate(x: _T | None = None) -> bool:
            return add_to_set(x, hash_set)

        return filter(predicate, xs)

    return delay(_arrow87)


def count_by(
    projection: Callable[[_T], _KEY], xs: IEnumerable_1[Any], comparer: IEqualityComparer_1[Any]
) -> IEnumerable_1[tuple[_KEY, int]]:
    def _arrow91(
        __unit: None = None, projection: Any = projection, xs: Any = xs, comparer: Any = comparer
    ) -> IEnumerable_1[tuple[_KEY, int]]:
        dict_1: Any = Dictionary([], comparer)
        keys: Array[_KEY] = []
        with get_enumerator(xs) as enumerator:
            while enumerator.System_Collections_IEnumerator_MoveNext():
                key: _KEY = projection(enumerator.System_Collections_Generic_IEnumerator_1_get_Current())
                match_value: tuple[bool, int]
                out_arg: int = 0

                def _arrow88(__unit: None = None) -> int:
                    return out_arg

                def _arrow89(v: int) -> None:
                    nonlocal out_arg
                    out_arg = v or 0

                match_value = (try_get_value(dict_1, key, FSharpRef(_arrow88, _arrow89)), out_arg)
                if match_value[0]:
                    dict_1[key] = match_value[1] + 1

                else:
                    dict_1[key] = 1
                    (keys.append(key))

        def _arrow90(key_1: _KEY | None = None) -> tuple[_KEY, int]:
            return (key_1, get_item_from_dict(dict_1, key_1))

        return map(_arrow90, keys)

    return delay(_arrow91)


def group_by(
    projection: Callable[[_T], _KEY], xs: IEnumerable_1[Any], comparer: IEqualityComparer_1[Any]
) -> IEnumerable_1[tuple[_KEY, IEnumerable_1[_T]]]:
    def _arrow95(
        __unit: None = None, projection: Any = projection, xs: Any = xs, comparer: Any = comparer
    ) -> IEnumerable_1[tuple[_KEY, IEnumerable_1[_T]]]:
        dict_1: Any = Dictionary([], comparer)
        keys: Array[_KEY] = []
        with get_enumerator(xs) as enumerator:
            while enumerator.System_Collections_IEnumerator_MoveNext():
                x: _T = enumerator.System_Collections_Generic_IEnumerator_1_get_Current()
                key: _KEY = projection(x)
                match_value: tuple[bool, Array[_T]]
                out_arg: Array[_T] = None

                def _arrow92(__unit: None = None) -> Array[_T]:
                    return out_arg

                def _arrow93(v: Array[_T]) -> None:
                    nonlocal out_arg
                    out_arg = v

                match_value = (try_get_value(dict_1, key, FSharpRef(_arrow92, _arrow93)), out_arg)
                if match_value[0]:
                    (match_value[1].append(x))

                else:
                    add_to_dict(dict_1, key, [x])
                    (keys.append(key))

        def _arrow94(key_1: _KEY | None = None) -> tuple[_KEY, IEnumerable_1[_T]]:
            return (key_1, get_item_from_dict(dict_1, key_1))

        return map(_arrow94, keys)

    return delay(_arrow95)


def Array_distinct(xs: Array[Any], comparer: IEqualityComparer_1[Any]) -> Array[Any]:
    return to_array(distinct(xs, comparer))


def Array_distinctBy(
    projection: Callable[[_T], _KEY], xs: Array[Any], comparer: IEqualityComparer_1[Any]
) -> Array[Any]:
    return to_array(distinct_by(projection, xs, comparer))


def Array_except(
    items_to_exclude: IEnumerable_1[Any], xs: Array[Any], comparer: IEqualityComparer_1[Any]
) -> Array[Any]:
    return to_array(except_(items_to_exclude, xs, comparer))


def Array_countBy(
    projection: Callable[[_T], _KEY], xs: Array[Any], comparer: IEqualityComparer_1[Any]
) -> Array[tuple[_KEY, int]]:
    return to_array(count_by(projection, xs, comparer))


def Array_groupBy(
    projection: Callable[[_T], _KEY], xs: Array[Any], comparer: IEqualityComparer_1[Any]
) -> Array[tuple[_KEY, Array[_T]]]:
    def mapping(
        tupled_arg: tuple[_KEY, IEnumerable_1[_T]], projection: Any = projection, xs: Any = xs, comparer: Any = comparer
    ) -> tuple[_KEY, Array[_T]]:
        return (tupled_arg[0], to_array(tupled_arg[1]))

    return to_array(map(mapping, group_by(projection, xs, comparer)))


def List_distinct(xs: FSharpList[Any], comparer: IEqualityComparer_1[Any]) -> FSharpList[Any]:
    return to_list(distinct(xs, comparer))


def List_distinctBy(
    projection: Callable[[_T], _KEY], xs: FSharpList[Any], comparer: IEqualityComparer_1[Any]
) -> FSharpList[Any]:
    return to_list(distinct_by(projection, xs, comparer))


def List_except(
    items_to_exclude: IEnumerable_1[Any], xs: FSharpList[Any], comparer: IEqualityComparer_1[Any]
) -> FSharpList[Any]:
    return to_list(except_(items_to_exclude, xs, comparer))


def List_countBy(
    projection: Callable[[_T], _KEY], xs: FSharpList[Any], comparer: IEqualityComparer_1[Any]
) -> FSharpList[tuple[_KEY, int]]:
    return to_list(count_by(projection, xs, comparer))


def List_groupBy(
    projection: Callable[[_T], _KEY], xs: FSharpList[Any], comparer: IEqualityComparer_1[Any]
) -> FSharpList[tuple[_KEY, FSharpList[_T]]]:
    def mapping(
        tupled_arg: tuple[_KEY, IEnumerable_1[_T]], projection: Any = projection, xs: Any = xs, comparer: Any = comparer
    ) -> tuple[_KEY, FSharpList[_T]]:
        return (tupled_arg[0], to_list(tupled_arg[1]))

    return to_list(map(mapping, group_by(projection, xs, comparer)))


__all__ = [
    "distinct",
    "distinct_by",
    "except_",
    "count_by",
    "group_by",
    "Array_distinct",
    "Array_distinctBy",
    "Array_except",
    "Array_countBy",
    "Array_groupBy",
    "List_distinct",
    "List_distinctBy",
    "List_except",
    "List_countBy",
    "List_groupBy",
]
