from collections.abc import Callable, Collection, Iterable
from dataclasses import dataclass
from typing import Any, Generic, Literal, TypeVar

from typing_extensions import override

from remedapy.local_types import SupportsGtLt

T = TypeVar('T')


U = TypeVar('U', bound=SupportsGtLt)

NonPositiveInt = Literal[0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10]


def is_persisted(iterable: Iterable[Any]) -> bool:
    """Return True if iterable supports multiple passes."""
    return isinstance(iterable, Collection) or hasattr(iterable, '__getitem__')


@dataclass
class LeaftistHeapNode(Generic[T, U]):
    value: T
    key: U
    index: int

    @override
    def __repr__(self):
        return f'{self.value}, {self.key}, {self.index}'


@dataclass
class LeaftistHeap(Generic[T, U]):
    node: 'LeaftistHeapNode[T, U]'
    left: 'LeaftistHeap[T, U] | None'
    right: 'LeaftistHeap[T, U] | None'

    @override
    def __repr__(self):
        return (
            f'{self.node.value}, l: {self.left.node.value if self.left else ""}, '
            f'r: {self.right.node.value if self.right else ""}'
        )


def _heapify(list_: list[LeaftistHeapNode[T, U]], n: int) -> LeaftistHeap[T, U] | None:
    # list is sorted by key
    if n >= len(list_):
        return None
    return LeaftistHeap(node=list_[n], left=_heapify(list_, 2 * n + 1), right=_heapify(list_, 2 * n + 2))


def heapify(iterable: Iterable[T], function: Callable[[T], U], /) -> 'LeaftistHeap[T, U] | None':
    list_ = (LeaftistHeapNode(value=x, key=function(x), index=i) for i, x in enumerate(iterable))
    return _heapify((sorted(list_, key=lambda x: x.key, reverse=True)), 0)


def _heap_insert(heap: 'LeaftistHeap[T, U]', node: 'LeaftistHeapNode[T, U]', /) -> None:
    if heap.left is not None and heap.left.node.key > node.key:
        # left child bigger than new
        if heap.right is not None and heap.right.node.key > heap.left.node.key:
            # right > left > new
            heap.node = heap.right.node
            _heap_insert(heap.right, node)
        else:
            # left > right idk new
            heap.node = heap.left.node
            _heap_insert(heap.left, node)
    elif heap.right is not None and heap.right.node.key > node.key:
        # right > (left idk new)
        heap.node = heap.right.node
        _heap_insert(heap.right, node)
    else:
        heap.node = node


def heap_maybe_insert(heap: 'LeaftistHeap[T, U]', node: 'LeaftistHeapNode[T, U]', /) -> 'LeaftistHeap[T, U]':
    if node.key > heap.node.key:
        return heap
    _heap_insert(heap, node)
    return heap


def _heap_to_list(heap: 'LeaftistHeap[T, U] | None', /) -> Iterable[LeaftistHeapNode[T, U]]:
    if heap is None:
        return
    yield heap.node
    yield from _heap_to_list(heap.left)
    yield from _heap_to_list(heap.right)


def heap_to_list(heap: 'LeaftistHeap[T, U]', /) -> list[T]:
    return [node.value for node in sorted(_heap_to_list(heap), key=lambda x: x.index)]


def indexes_in_heap(heap: 'LeaftistHeap[T, U]', /) -> set[int]:
    return {node.index for node in _heap_to_list(heap)}
