# -*- coding: utf-8 -*-
"""
@File    : __init__.py
@Author  : Jay
@Date    : 2026/3/19 18:01
@Desc    : Utilities to detect synchronous vs asynchronous call context
         and to decorate async functions to fallback to sync implementations.
"""
import inspect
import sys
from functools import wraps
from typing import Callable, Any

# Flags from code objects that indicate coroutine behavior
_CO_FROM_COROUTINE = (
        inspect.CO_COROUTINE
        | inspect.CO_ITERABLE_COROUTINE
        | inspect.CO_ASYNC_GENERATOR
)


def is_coroutine_context(level: int = 2) -> bool:
    """Determine if the current call context is asynchronous (coroutine).

    Args:
        level (int): Stack level to inspect (default 2). Increase if called from wrapper functions.

    Returns:
        bool: True if the current frame is part of a coroutine context, False if synchronous.
    """
    frame = sys._getframe(level)
    f_code = frame.f_code

    # Check if the code object has coroutine flags
    if f_code.co_flags & _CO_FROM_COROUTINE:
        return True

    # Handle nested "<...>" function wrappers (like decorators)
    if f_code.co_flags & inspect.CO_NESTED and f_code.co_name[0] == '<':
        return is_coroutine_context(level + 2)

    return False


def awaitable(sync_func: Callable) -> Callable:
    """Decorator for async functions to use a synchronous fallback when called from sync context.

    Usage:
        def foo_sync(x, y): ...

        @awaitable(foo_sync)
        async def foo_async(x, y): ...

    Args:
        sync_func (Callable): Synchronous implementation of the async function.

    Returns:
        Callable: Decorated async function that uses `sync_func` if called synchronously.
    """

    def decorator(async_func: Callable) -> Callable:
        # Signature check to ensure sync/async match
        sig_sync = inspect.signature(sync_func)
        sig_async = inspect.signature(async_func)
        if sig_sync != sig_async:
            raise TypeError(
                f"{sync_func.__name__} and async {async_func.__name__} signatures differ:\n"
                f"  sync: {sig_sync}\n"
                f"  async: {sig_async}"
            )

        @wraps(async_func)
        def wrapper(*args: Any, **kwargs: Any) -> Any:
            # If currently inside a coroutine, run async function
            if is_coroutine_context():
                return async_func(*args, **kwargs)

            # Otherwise, run synchronous version
            result = sync_func(*args, **kwargs)
            if inspect.iscoroutine(result):
                raise RuntimeError(
                    f"Synchronous function {sync_func.__name__} returned a coroutine object. "
                    f"Ensure it is a true synchronous implementation."
                )
            return result

        # Attach references for introspection
        wrapper._sync_func = sync_func
        wrapper._async_func = async_func
        wrapper._awaitable = True
        return wrapper

    return decorator


__all__ = ["awaitable", "is_coroutine_context"]

if __name__ == '__main__':
    # Demonstration of `awaitable` decorator usage
    class Limited:
        def _ask_available_container_sync(self, quantity):
            """Synchronous fallback implementation."""
            return "sync-"

        @awaitable(_ask_available_container_sync)
        async def ask_available_container(self, quantity):
            """Async implementation."""
            return "async+"


    lt = Limited()

    # Synchronous call
    res_sync = lt.ask_available_container(1)
    print("Sync result:", res_sync)

    # Asynchronous call
    import asyncio


    async def main():
        res_async = await lt.ask_available_container(2)
        print("Async result:", res_async)


    asyncio.run(main())
