from __future__ import annotations

import asyncio
from concurrent.futures import ThreadPoolExecutor
from typing import Awaitable, TypeVar


def raise_if_running_event_loop(op: str, *, use_in_async: str) -> None:
    """Raise if called from a running event loop.

    This is used to guard *blocking* sync APIs (constructors or facades) from being
    accidentally invoked in async code, where they would block the loop.
    """
    try:
        asyncio.get_running_loop()
    except RuntimeError:
        return

    raise RuntimeError(f"{op}() is synchronous and may block the event loop. Use {use_in_async}.")


T = TypeVar("T")


def run_sync(awaitable: Awaitable[T], *, timeout_s: float | None = None) -> T:
    """Run an awaitable from synchronous code in a deadlock-safe way.

    Semantics:
    - If no event loop is running in the current thread: run via `asyncio.run(...)`.
    - If an event loop is running in the current thread: run the awaitable in a
      dedicated background thread with its own event loop and block for the result.

    Note: Calling this from a running event loop thread will still *block* that
    thread (it just avoids deadlocking the loop by executing the awaitable in a
    different thread).
    """

    async def _wrap() -> T:
        if timeout_s is None:
            return await awaitable
        return await asyncio.wait_for(awaitable, timeout=timeout_s)

    try:
        asyncio.get_running_loop()
    except RuntimeError:
        return asyncio.run(_wrap())

    # There is a running loop in this thread. Avoid `run_coroutine_threadsafe(...).result()`
    # against an unknown loop/thread combination; instead run in a fresh loop on a
    # dedicated background thread.
    def _run_in_thread() -> T:
        return asyncio.run(_wrap())

    with ThreadPoolExecutor(max_workers=1, thread_name_prefix="relationalai-run-sync") as ex:
        fut = ex.submit(_run_in_thread)
        return fut.result(timeout=timeout_s)

