# SPDX-License-Identifier: Apache-2.0
# Copyright 2025 Bubble Sort Implementation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Bubble Sort Algorithm Implementation.

This module provides an implementation of the bubble sort algorithm,
a simple comparison-based sorting algorithm that repeatedly steps through
the list, compares adjacent elements and swaps them if they are in the
wrong order.

Typical usage example:
    numbers = [64, 34, 25, 12, 22, 11, 90]
    sorted_numbers = bubble_sort(numbers)
    print(sorted_numbers)  # [11, 12, 22, 25, 34, 64, 90]
"""

from typing import List, TypeVar

# Type variable for generic sorting
T = TypeVar('T')


def bubble_sort(arr: List[T]) -> List[T]:
    """Sort a list using the bubble sort algorithm.

    Bubble sort is a simple sorting algorithm that repeatedly steps through
    the list, compares adjacent elements and swaps them if they are in the
    wrong order. The pass through the list is repeated until the list is sorted.

    Time Complexity: O(n²) in worst and average cases, O(n) in best case
    Space Complexity: O(1) - sorts in place

    Args:
        arr: List of comparable elements to be sorted.

    Returns:
        A new list containing the sorted elements in ascending order.

    Raises:
        TypeError: If the list contains elements that cannot be compared.

    Example:
        >>> bubble_sort([64, 34, 25, 12, 22, 11, 90])
        [11, 12, 22, 25, 34, 64, 90]
        >>> bubble_sort(['banana', 'apple', 'cherry'])
        ['apple', 'banana', 'cherry']
    """
    # Create a copy of the input list to avoid modifying the original
    arr_copy = arr.copy()
    n = len(arr_copy)
    
    # Handle edge cases
    if n <= 1:
        return arr_copy
    
    # Outer loop for number of passes
    # We need at most n-1 passes to sort n elements
    for i in range(n):
        # Flag to track if any swaps were made in this pass
        # If no swaps are made, the array is already sorted
        swapped = False
        
        # Inner loop for comparisons in each pass
        # Last i elements are already in their correct position
        # so we can reduce the range by i in each iteration
        for j in range(0, n - i - 1):
            # Compare adjacent elements
            if arr_copy[j] > arr_copy[j + 1]:
                # Swap elements if they are in wrong order
                arr_copy[j], arr_copy[j + 1] = arr_copy[j + 1], arr_copy[j]
                swapped = True
        
        # If no swapping occurred, array is sorted
        # This optimization reduces best-case time complexity to O(n)
        if not swapped:
            break
    
    return arr_copy


def bubble_sort_verbose(arr: List[T], show_steps: bool = False) -> List[T]:
    """Sort a list using bubble sort with optional step-by-step output.
    
    This version provides detailed output of each step in the sorting process,
    useful for educational purposes or debugging.
    
    Args:
        arr: List of comparable elements to be sorted.
        show_steps: If True, prints each step of the sorting process.
        
    Returns:
        A new list containing the sorted elements in ascending order.
    """
    arr_copy = arr.copy()
    n = len(arr_copy)
    
    if show_steps:
        print(f"Initial array: {arr_copy}")
    
    if n <= 1:
        return arr_copy
    
    for i in range(n):
        swapped = False
        
        if show_steps:
            print(f"\nPass {i + 1}:")
        
        for j in range(0, n - i - 1):
            if show_steps:
                print(f"  Comparing {arr_copy[j]} and {arr_copy[j + 1]}")
            
            if arr_copy[j] > arr_copy[j + 1]:
                arr_copy[j], arr_copy[j + 1] = arr_copy[j + 1], arr_copy[j]
                swapped = True
                
                if show_steps:
                    print(f"    Swapped! Array: {arr_copy}")
            elif show_steps:
                print(f"    No swap needed")
        
        if not swapped:
            if show_steps:
                print(f"  No swaps in this pass - array is sorted!")
            break
    
    if show_steps:
        print(f"\nFinal sorted array: {arr_copy}")
    
    return arr_copy


if __name__ == "__main__":
    # Example usage and testing
    test_cases = [
        [64, 34, 25, 12, 22, 11, 90],
        [5, 2, 8, 1, 9],
        [1],  # Single element
        [],   # Empty list
        [1, 2, 3, 4, 5],  # Already sorted
        [5, 4, 3, 2, 1],  # Reverse sorted
        ['banana', 'apple', 'cherry', 'date'],  # Strings
    ]
    
    print("Bubble Sort Algorithm Demonstration")
    print("=" * 40)
    
    for i, test_case in enumerate(test_cases, 1):
        print(f"\nTest Case {i}: {test_case}")
        sorted_result = bubble_sort(test_case)
        print(f"Sorted Result: {sorted_result}")
    
    # Demonstrate verbose version
    print("\n" + "=" * 40)
    print("Verbose Bubble Sort Demonstration")
    print("=" * 40)
    
    sample_array = [64, 34, 25, 12, 22, 11, 90]
    bubble_sort_verbose(sample_array, show_steps=True)