"""Decorators that wrap functions to return Result instead of raising."""

from __future__ import annotations

import functools
import traceback
from typing import TYPE_CHECKING

from .result import Err, Ok

if TYPE_CHECKING:
    import sys
    from collections.abc import Callable
    from typing import TypeVar

    if sys.version_info >= (3, 10):
        from typing import Concatenate, ParamSpec
    else:
        from typing_extensions import Concatenate, ParamSpec

    from .result import Result

    T = TypeVar("T")
    P = ParamSpec("P")
    U = TypeVar("U")


def safe(
    func: Callable[P, U],
) -> Callable[P, Result[U, Exception]]:
    """
    Wrap *func* so exceptions become *Err* values.
    """

    @functools.wraps(func)
    def wrapper(*args: P.args, **kwargs: P.kwargs) -> Result[U, Exception]:
        """Call the wrapped function and capture exceptions as Err."""
        try:
            return Ok(func(*args, **kwargs))
        except Exception as e:
            return Err(
                error=e,
                input_args=(args, kwargs),
                traceback=traceback.format_exc(),
            )

    return wrapper


def safe_method(
    func: Callable[Concatenate[T, P], U],
) -> Callable[Concatenate[T, P], Result[U, Exception]]:
    """
    Wrap a method so exceptions become *Err* values.
    """

    @functools.wraps(func)
    def wrapper(
        self: T,
        *args: P.args,
        **kwargs: P.kwargs,
    ) -> Result[U, Exception]:
        """Call the wrapped method and capture exceptions as Err."""
        try:
            return Ok(func(self, *args, **kwargs))
        except Exception as e:
            return Err(
                error=e,
                input_args=(self, args, kwargs),
                traceback=traceback.format_exc(),
            )

    return wrapper  # type: ignore[return-value]
