"""Retry a synchronous callable with exponential backoff."""

import time
from typing import Callable, TypeVar

from classify_api_error import classify_api_error
from compute_backoff_delay import compute_backoff_delay

T = TypeVar("T")


def retry_sync(
    func: Callable[[], T],
    max_retries: int,
    base_delay: float = 0.5,
    max_delay: float = 8.0,
    jitter: bool = True,
    is_retryable: Callable[[Exception], bool] | None = None,
) -> T:
    """Retry a synchronous function on transient errors with exponential backoff.

    Args:
        func: Zero-argument callable to execute and retry.
        max_retries: Maximum number of retry attempts after the first failure.
        base_delay: Base delay in seconds for backoff calculation.
        max_delay: Maximum delay cap in seconds.
        jitter: If True, adds random jitter to backoff delay.
        is_retryable: Optional predicate; if None, uses classify_api_error to
                      classify transient errors as retryable.

    Returns:
        The return value of func on success.

    Raises:
        Exception: The last exception raised if all attempts are exhausted or
                   if the error is not retryable.
    """
    if is_retryable is None:
        is_retryable = lambda e: classify_api_error(e) == "transient"

    last_exc: Exception | None = None
    for attempt in range(max_retries + 1):
        try:
            return func()
        except Exception as exc:
            last_exc = exc
            if not is_retryable(exc) or attempt >= max_retries:
                raise
            delay = compute_backoff_delay(attempt, base_delay, max_delay, jitter)
            time.sleep(delay)

    raise last_exc  # unreachable but satisfies type checkers
