#!/usr/bin/env python3
# chuk_mcp_math/arithmetic/comparison/extrema.py
"""
Extrema and Ordering Operations - Async Native

Functions for finding minimum and maximum values, clamping, and ordering operations.

Functions:
- minimum, maximum, clamp
- sort_numbers, rank_numbers
"""

import asyncio
from typing import Union, List
from chuk_mcp_math.mcp_decorator import mcp_function

Number = Union[int, float]


@mcp_function(
    description="Find the smaller of two numbers.",
    namespace="arithmetic",
    category="comparison",
    execution_modes=["local", "remote"],
    examples=[
        {"input": {"a": 5, "b": 3}, "output": 3, "description": "Minimum of 5 and 3"},
        {
            "input": {"a": -2, "b": 1},
            "output": -2,
            "description": "Minimum with negative number",
        },
        {
            "input": {"a": 7.5, "b": 7.5},
            "output": 7.5,
            "description": "Minimum of equal numbers",
        },
        {
            "input": {"a": 2.3, "b": 2.7},
            "output": 2.3,
            "description": "Minimum of decimals",
        },
    ],
)
async def minimum(a: Number, b: Number) -> Number:
    """
    Find the smaller of two numbers.

    Args:
        a: First number
        b: Second number

    Returns:
        The smaller of a and b

    Example:
        minimum(5, 3) = 3
        minimum(-2, 1) = -2
        minimum(7.5, 7.5) = 7.5
    """
    return min(a, b)


@mcp_function(
    description="Find the larger of two numbers.",
    namespace="arithmetic",
    category="comparison",
    execution_modes=["local", "remote"],
    examples=[
        {"input": {"a": 5, "b": 3}, "output": 5, "description": "Maximum of 5 and 3"},
        {
            "input": {"a": -2, "b": 1},
            "output": 1,
            "description": "Maximum with negative number",
        },
        {
            "input": {"a": 7.5, "b": 7.5},
            "output": 7.5,
            "description": "Maximum of equal numbers",
        },
        {
            "input": {"a": 2.3, "b": 2.7},
            "output": 2.7,
            "description": "Maximum of decimals",
        },
    ],
)
async def maximum(a: Number, b: Number) -> Number:
    """
    Find the larger of two numbers.

    Args:
        a: First number
        b: Second number

    Returns:
        The larger of a and b

    Example:
        maximum(5, 3) = 5
        maximum(-2, 1) = 1
        maximum(7.5, 7.5) = 7.5
    """
    return max(a, b)


@mcp_function(
    description="Clamp a value between a minimum and maximum bound. Ensures the value stays within specified limits.",
    namespace="arithmetic",
    category="comparison",
    execution_modes=["local", "remote"],
    examples=[
        {
            "input": {"value": 5, "min_val": 1, "max_val": 10},
            "output": 5,
            "description": "Value within bounds",
        },
        {
            "input": {"value": -2, "min_val": 1, "max_val": 10},
            "output": 1,
            "description": "Value below minimum",
        },
        {
            "input": {"value": 15, "min_val": 1, "max_val": 10},
            "output": 10,
            "description": "Value above maximum",
        },
        {
            "input": {"value": 1, "min_val": 1, "max_val": 10},
            "output": 1,
            "description": "Value at minimum",
        },
    ],
)
async def clamp(value: Number, min_val: Number, max_val: Number) -> Number:
    """
    Clamp a value between minimum and maximum bounds.

    Args:
        value: The value to clamp
        min_val: The minimum allowed value
        max_val: The maximum allowed value

    Returns:
        The clamped value (between min_val and max_val inclusive)

    Raises:
        ValueError: If min_val > max_val

    Example:
        clamp(5, 1, 10) = 5
        clamp(-2, 1, 10) = 1
        clamp(15, 1, 10) = 10
    """
    if min_val > max_val:
        raise ValueError("Minimum value cannot be greater than maximum value")

    return max(min_val, min(value, max_val))


@mcp_function(
    description="Sort a list of numbers in ascending or descending order.",
    namespace="arithmetic",
    category="comparison",
    execution_modes=["local", "remote"],
    examples=[
        {
            "input": {"numbers": [3, 1, 4, 1, 5], "descending": False},
            "output": [1, 1, 3, 4, 5],
            "description": "Sort ascending",
        },
        {
            "input": {"numbers": [3, 1, 4, 1, 5], "descending": True},
            "output": [5, 4, 3, 1, 1],
            "description": "Sort descending",
        },
        {
            "input": {"numbers": [2.5, 1.1, 3.7], "descending": False},
            "output": [1.1, 2.5, 3.7],
            "description": "Sort floats",
        },
        {
            "input": {"numbers": [-2, 0, 1], "descending": False},
            "output": [-2, 0, 1],
            "description": "Sort with negatives",
        },
    ],
)
async def sort_numbers(numbers: List[Number], descending: bool = False) -> List[Number]:
    """
    Sort a list of numbers in ascending or descending order.

    Args:
        numbers: List of numbers to sort
        descending: If True, sort in descending order (default: False for ascending)

    Returns:
        New sorted list of numbers

    Example:
        sort_numbers([3, 1, 4, 1, 5]) = [1, 1, 3, 4, 5]
        sort_numbers([3, 1, 4, 1, 5], descending=True) = [5, 4, 3, 1, 1]
    """
    # For large lists, yield control during sorting
    if len(numbers) > 1000:
        await asyncio.sleep(0)

    return sorted(numbers, reverse=descending)


@mcp_function(
    description="Get the rank (1-based position) of each number in a list when sorted. Handles ties appropriately.",
    namespace="arithmetic",
    category="comparison",
    execution_modes=["local", "remote"],
    examples=[
        {
            "input": {"numbers": [3, 1, 4, 1, 5]},
            "output": [3, 1, 4, 1, 5],
            "description": "Ranks with ties",
        },
        {
            "input": {"numbers": [10, 20, 30]},
            "output": [1, 2, 3],
            "description": "Simple ranking",
        },
        {
            "input": {"numbers": [1.5, 2.5, 1.5]},
            "output": [1, 3, 1],
            "description": "Ranking with float ties",
        },
    ],
)
async def rank_numbers(numbers: List[Number]) -> List[int]:
    """
    Get the rank (1-based position) of each number when sorted in ascending order.

    Args:
        numbers: List of numbers to rank

    Returns:
        List of ranks corresponding to each input number

    Example:
        rank_numbers([3, 1, 4, 1, 5]) = [3, 1, 4, 1, 5]
        rank_numbers([10, 20, 30]) = [1, 2, 3]
    """
    # For large lists, yield control during processing
    if len(numbers) > 1000:
        await asyncio.sleep(0)

    # Create list of (value, original_index) pairs
    indexed_numbers = [(num, i) for i, num in enumerate(numbers)]

    # Sort by value
    sorted_indexed = sorted(indexed_numbers, key=lambda x: x[0])

    # Assign ranks
    ranks = [0] * len(numbers)
    current_rank = 1

    for i, (value, original_idx) in enumerate(sorted_indexed):
        if i > 0 and value != sorted_indexed[i - 1][0]:
            current_rank = i + 1
        ranks[original_idx] = current_rank

    return ranks


@mcp_function(
    description="Find the minimum value in a list of numbers.",
    namespace="arithmetic",
    category="comparison",
    execution_modes=["local", "remote"],
    examples=[
        {
            "input": {"numbers": [3, 1, 4, 1, 5]},
            "output": 1,
            "description": "Minimum of list",
        },
        {
            "input": {"numbers": [-2, 0, 1]},
            "output": -2,
            "description": "Minimum with negatives",
        },
        {
            "input": {"numbers": [2.5, 1.1, 3.7]},
            "output": 1.1,
            "description": "Minimum of floats",
        },
        {"input": {"numbers": [42]}, "output": 42, "description": "Single element"},
    ],
)
async def min_list(numbers: List[Number]) -> Number:
    """
    Find the minimum value in a list of numbers.

    Args:
        numbers: List of numbers (cannot be empty)

    Returns:
        The minimum value in the list

    Raises:
        ValueError: If list is empty

    Example:
        min_list([3, 1, 4, 1, 5]) = 1
        min_list([-2, 0, 1]) = -2
        min_list([2.5, 1.1, 3.7]) = 1.1
    """
    if not numbers:
        raise ValueError("Cannot find minimum of empty list")

    # For large lists, yield control
    if len(numbers) > 1000:
        await asyncio.sleep(0)

    return min(numbers)


@mcp_function(
    description="Find the maximum value in a list of numbers.",
    namespace="arithmetic",
    category="comparison",
    execution_modes=["local", "remote"],
    examples=[
        {
            "input": {"numbers": [3, 1, 4, 1, 5]},
            "output": 5,
            "description": "Maximum of list",
        },
        {
            "input": {"numbers": [-2, 0, 1]},
            "output": 1,
            "description": "Maximum with negatives",
        },
        {
            "input": {"numbers": [2.5, 1.1, 3.7]},
            "output": 3.7,
            "description": "Maximum of floats",
        },
        {"input": {"numbers": [42]}, "output": 42, "description": "Single element"},
    ],
)
async def max_list(numbers: List[Number]) -> Number:
    """
    Find the maximum value in a list of numbers.

    Args:
        numbers: List of numbers (cannot be empty)

    Returns:
        The maximum value in the list

    Raises:
        ValueError: If list is empty

    Example:
        max_list([3, 1, 4, 1, 5]) = 5
        max_list([-2, 0, 1]) = 1
        max_list([2.5, 1.1, 3.7]) = 3.7
    """
    if not numbers:
        raise ValueError("Cannot find maximum of empty list")

    # For large lists, yield control
    if len(numbers) > 1000:
        await asyncio.sleep(0)

    return max(numbers)


# Export all extrema and ordering functions
__all__ = [
    "minimum",
    "maximum",
    "clamp",
    "sort_numbers",
    "rank_numbers",
    "min_list",
    "max_list",
]

if __name__ == "__main__":
    import asyncio

    async def test_extrema_operations():
        """Test all extrema and ordering operations."""
        print("🔍 Extrema and Ordering Operations Test")
        print("=" * 40)

        # Test min/max operations
        print(f"minimum(5, 3) = {await minimum(5, 3)}")
        print(f"maximum(5, 3) = {await maximum(5, 3)}")
        print(f"clamp(15, 1, 10) = {await clamp(15, 1, 10)}")

        # Test list operations
        test_list = [3, 1, 4, 1, 5]
        print(f"sort_numbers({test_list}) = {await sort_numbers(test_list)}")
        print(
            f"sort_numbers({test_list}, descending=True) = {await sort_numbers(test_list, descending=True)}"
        )
        print(f"rank_numbers({test_list}) = {await rank_numbers(test_list)}")
        print(f"min_list({test_list}) = {await min_list(test_list)}")
        print(f"max_list({test_list}) = {await max_list(test_list)}")

        print("\n✅ All extrema and ordering operations working!")

    asyncio.run(test_extrema_operations())
