"""
autobee.structures — Stack, Queue, LinkedList, BST.
"""

from collections import deque


class Stack:
    """
    LIFO Stack backed by a Python list.

    Example:
        >>> from autobee import Stack
        >>> s = Stack()
        >>> s.push(1); s.push(2); s.push(3)
        >>> s.pop()
        3
    """

    def __init__(self):
        self._data = []

    def push(self, value) -> None:
        """Push a value onto the top of the stack."""
        self._data.append(value)

    def pop(self):
        """Pop and return the top value. Returns None if empty."""
        return self._data.pop() if self._data else None

    def peek(self):
        """Return top value without removing it. Returns None if empty."""
        return self._data[-1] if self._data else None

    def is_empty(self) -> bool:
        return len(self._data) == 0

    def size(self) -> int:
        return len(self._data)

    def to_list(self) -> list:
        return self._data[:]

    def __repr__(self):
        return f"Stack({self._data})"


class Queue:
    """
    FIFO Queue backed by collections.deque.

    Example:
        >>> from autobee import Queue
        >>> q = Queue()
        >>> q.enqueue("a"); q.enqueue("b")
        >>> q.dequeue()
        'a'
    """

    def __init__(self):
        self._data = deque()

    def enqueue(self, value) -> None:
        """Add value to the back of the queue."""
        self._data.append(value)

    def dequeue(self):
        """Remove and return value from the front. Returns None if empty."""
        return self._data.popleft() if self._data else None

    def peek(self):
        """Return front value without removing. Returns None if empty."""
        return self._data[0] if self._data else None

    def is_empty(self) -> bool:
        return len(self._data) == 0

    def size(self) -> int:
        return len(self._data)

    def to_list(self) -> list:
        return list(self._data)

    def __repr__(self):
        return f"Queue({list(self._data)})"


class _LLNode:
    def __init__(self, val):
        self.val = val
        self.next = None


class LinkedList:
    """
    Singly Linked List.

    Example:
        >>> from autobee import LinkedList
        >>> ll = LinkedList()
        >>> ll.append(1); ll.append(2); ll.append(3)
        >>> ll.to_list()
        [1, 2, 3]
        >>> ll.delete(2)
        >>> str(ll)
        '1 -> 3'
    """

    def __init__(self):
        self.head = None
        self._size = 0

    def append(self, val) -> None:
        """Append value at the end."""
        node = _LLNode(val)
        if not self.head:
            self.head = node
        else:
            cur = self.head
            while cur.next:
                cur = cur.next
            cur.next = node
        self._size += 1

    def prepend(self, val) -> None:
        """Insert value at the front."""
        node = _LLNode(val)
        node.next = self.head
        self.head = node
        self._size += 1

    def delete(self, val) -> bool:
        """Delete first occurrence of val. Returns True if deleted."""
        if not self.head:
            return False
        if self.head.val == val:
            self.head = self.head.next
            self._size -= 1
            return True
        cur = self.head
        while cur.next:
            if cur.next.val == val:
                cur.next = cur.next.next
                self._size -= 1
                return True
            cur = cur.next
        return False

    def search(self, val) -> bool:
        """Return True if val exists in the list."""
        cur = self.head
        while cur:
            if cur.val == val:
                return True
            cur = cur.next
        return False

    def to_list(self) -> list:
        result, cur = [], self.head
        while cur:
            result.append(cur.val)
            cur = cur.next
        return result

    def size(self) -> int:
        return self._size

    def __repr__(self):
        return " -> ".join(map(str, self.to_list()))

    def __str__(self):
        return self.__repr__()


class _BSTNode:
    def __init__(self, val):
        self.val = val
        self.left = self.right = None


class BST:
    """
    Binary Search Tree with insert, search, delete, and traversals.

    Example:
        >>> from autobee import BST
        >>> tree = BST()
        >>> for v in [5, 3, 7, 1, 4]: tree.insert(v)
        >>> tree.inorder()
        [1, 3, 4, 5, 7]
        >>> tree.search(4)
        True
    """

    def __init__(self):
        self.root = None

    def insert(self, val) -> None:
        """Insert a value into the BST."""
        def _ins(node, val):
            if not node:
                return _BSTNode(val)
            if val < node.val:
                node.left = _ins(node.left, val)
            elif val > node.val:
                node.right = _ins(node.right, val)
            return node
        self.root = _ins(self.root, val)

    def search(self, val) -> bool:
        """Return True if val exists in the BST."""
        def _s(node, val):
            if not node:
                return False
            if val == node.val:
                return True
            return _s(node.left, val) if val < node.val else _s(node.right, val)
        return _s(self.root, val)

    def delete(self, val) -> None:
        """Delete a value from the BST."""
        def _min(node):
            while node.left:
                node = node.left
            return node

        def _del(node, val):
            if not node:
                return node
            if val < node.val:
                node.left = _del(node.left, val)
            elif val > node.val:
                node.right = _del(node.right, val)
            else:
                if not node.left:
                    return node.right
                if not node.right:
                    return node.left
                successor = _min(node.right)
                node.val = successor.val
                node.right = _del(node.right, successor.val)
            return node

        self.root = _del(self.root, val)

    def inorder(self) -> list:
        """Return inorder (sorted) traversal."""
        result = []
        def _in(node):
            if node:
                _in(node.left)
                result.append(node.val)
                _in(node.right)
        _in(self.root)
        return result

    def preorder(self) -> list:
        """Return preorder traversal."""
        result = []
        def _pre(node):
            if node:
                result.append(node.val)
                _pre(node.left)
                _pre(node.right)
        _pre(self.root)
        return result

    def postorder(self) -> list:
        """Return postorder traversal."""
        result = []
        def _post(node):
            if node:
                _post(node.left)
                _post(node.right)
                result.append(node.val)
        _post(self.root)
        return result

    def height(self) -> int:
        """Return height of the tree."""
        def _h(node):
            if not node:
                return 0
            return 1 + max(_h(node.left), _h(node.right))
        return _h(self.root)

    def to_dict(self) -> dict:
        """Serialize tree to nested dict (useful for visualization)."""
        def _d(node):
            if not node:
                return None
            return {"val": node.val, "left": _d(node.left), "right": _d(node.right)}
        return _d(self.root)

    def __repr__(self):
        return f"BST(inorder={self.inorder()})"
