"""Retry logic for transient failures."""

from __future__ import annotations

import time
from collections.abc import Callable
from typing import Any

from ilum.errors import ClusterConnectionError

_DEFAULT_RETRYABLE = (ClusterConnectionError,)


def with_retry(
    fn: Callable[..., Any],
    *args: object,
    max_attempts: int = 3,
    backoff: float = 2.0,
    retryable: tuple[type[Exception], ...] = _DEFAULT_RETRYABLE,
    **kwargs: object,
) -> Any:
    """Call *fn* with retries on transient failures.

    Parameters
    ----------
    fn:
        The callable to execute.
    max_attempts:
        Maximum number of attempts (including the first).
    backoff:
        Multiplier for exponential backoff between retries.
    retryable:
        Exception types that trigger a retry.
    """
    last_exc: Exception | None = None
    for attempt in range(1, max_attempts + 1):
        try:
            return fn(*args, **kwargs)
        except retryable as exc:
            last_exc = exc
            if attempt < max_attempts:
                sleep_time = backoff ** (attempt - 1)
                time.sleep(sleep_time)
    raise last_exc  # type: ignore[misc]
