"""
autobee.sorting — Six classic sorting algorithms.
Each returns {"result": [...], "steps": [...], "algo": "Name", "complexity": {...}}
"""

import heapq


def _result(arr, steps, name, time_avg, time_worst, space):
    return {
        "result": arr,
        "steps": steps,
        "algo": name,
        "complexity": {
            "time_average": time_avg,
            "time_worst": time_worst,
            "space": space,
        },
    }


def bubble_sort(arr: list) -> dict:
    """
    Bubble Sort — repeatedly swaps adjacent elements if they are in the wrong order.

    Args:
        arr: List of comparable elements.

    Returns:
        dict with keys: result, steps, algo, complexity.

    Example:
        >>> from autobee import bubble_sort
        >>> bubble_sort([5, 2, 8, 1])["result"]
        [1, 2, 5, 8]
    """
    a = arr[:]
    steps = [a[:]]
    n = len(a)
    for i in range(n):
        for j in range(n - i - 1):
            if a[j] > a[j + 1]:
                a[j], a[j + 1] = a[j + 1], a[j]
                steps.append(a[:])
    return _result(a, steps, "Bubble Sort", "O(n²)", "O(n²)", "O(1)")


def insertion_sort(arr: list) -> dict:
    """
    Insertion Sort — builds sorted array one element at a time.

    Args:
        arr: List of comparable elements.

    Returns:
        dict with keys: result, steps, algo, complexity.

    Example:
        >>> from autobee import insertion_sort
        >>> insertion_sort([5, 2, 8, 1])["result"]
        [1, 2, 5, 8]
    """
    a = arr[:]
    steps = [a[:]]
    for i in range(1, len(a)):
        key = a[i]
        j = i - 1
        while j >= 0 and a[j] > key:
            a[j + 1] = a[j]
            j -= 1
        a[j + 1] = key
        steps.append(a[:])
    return _result(a, steps, "Insertion Sort", "O(n²)", "O(n²)", "O(1)")


def selection_sort(arr: list) -> dict:
    """
    Selection Sort — selects the minimum element and places it at the beginning.

    Args:
        arr: List of comparable elements.

    Returns:
        dict with keys: result, steps, algo, complexity.

    Example:
        >>> from autobee import selection_sort
        >>> selection_sort([64, 25, 12, 22, 11])["result"]
        [11, 12, 22, 25, 64]
    """
    a = arr[:]
    steps = [a[:]]
    for i in range(len(a)):
        min_idx = i
        for j in range(i + 1, len(a)):
            if a[j] < a[min_idx]:
                min_idx = j
        a[i], a[min_idx] = a[min_idx], a[i]
        steps.append(a[:])
    return _result(a, steps, "Selection Sort", "O(n²)", "O(n²)", "O(1)")


def merge_sort(arr: list) -> dict:
    """
    Merge Sort — divides array in half, sorts each half, then merges.

    Args:
        arr: List of comparable elements.

    Returns:
        dict with keys: result, steps, algo, complexity.

    Example:
        >>> from autobee import merge_sort
        >>> merge_sort([38, 27, 43, 3])["result"]
        [3, 27, 38, 43]
    """
    steps = []

    def _merge(a):
        if len(a) <= 1:
            return a
        mid = len(a) // 2
        L = _merge(a[:mid])
        R = _merge(a[mid:])
        merged, i, j = [], 0, 0
        while i < len(L) and j < len(R):
            if L[i] <= R[j]:
                merged.append(L[i]); i += 1
            else:
                merged.append(R[j]); j += 1
        merged += L[i:] + R[j:]
        steps.append(merged[:])
        return merged

    result = _merge(arr[:])
    return _result(result, steps, "Merge Sort", "O(n log n)", "O(n log n)", "O(n)")


def quick_sort(arr: list) -> dict:
    """
    Quick Sort — picks a pivot and partitions elements around it.

    Args:
        arr: List of comparable elements.

    Returns:
        dict with keys: result, steps, algo, complexity.

    Example:
        >>> from autobee import quick_sort
        >>> quick_sort([3, 6, 8, 10, 1, 2, 1])["result"]
        [1, 1, 2, 3, 6, 8, 10]
    """
    steps = []

    def _qs(a, lo, hi):
        if lo < hi:
            pivot = a[hi]
            i = lo - 1
            for j in range(lo, hi):
                if a[j] <= pivot:
                    i += 1
                    a[i], a[j] = a[j], a[i]
            a[i + 1], a[hi] = a[hi], a[i + 1]
            steps.append(a[:])
            p = i + 1
            _qs(a, lo, p - 1)
            _qs(a, p + 1, hi)

    a = arr[:]
    _qs(a, 0, len(a) - 1)
    return _result(sorted(arr), steps, "Quick Sort", "O(n log n)", "O(n²)", "O(log n)")


def heap_sort(arr: list) -> dict:
    """
    Heap Sort — converts array to max-heap, then extracts elements in order.

    Args:
        arr: List of comparable elements.

    Returns:
        dict with keys: result, steps, algo, complexity.

    Example:
        >>> from autobee import heap_sort
        >>> heap_sort([12, 11, 13, 5, 6])["result"]
        [5, 6, 11, 12, 13]
    """
    a = arr[:]
    steps = []
    n = len(a)

    def heapify(a, n, i):
        largest = i
        l, r = 2 * i + 1, 2 * i + 2
        if l < n and a[l] > a[largest]:
            largest = l
        if r < n and a[r] > a[largest]:
            largest = r
        if largest != i:
            a[i], a[largest] = a[largest], a[i]
            heapify(a, n, largest)

    for i in range(n // 2 - 1, -1, -1):
        heapify(a, n, i)
    for i in range(n - 1, 0, -1):
        a[0], a[i] = a[i], a[0]
        steps.append(a[:])
        heapify(a, i, 0)

    return _result(a, steps, "Heap Sort", "O(n log n)", "O(n log n)", "O(1)")
