"""Shell escaping and async utilities."""

import asyncio
import re
import shlex
from typing import Any, Coroutine

_CMD_RE = re.compile(r"^[A-Za-z0-9._/\-]+$")


def shell_escape(cmd: str, args: list[str] | None = None) -> str:
    """
    Build a shell-safe command string.
    Uses shlex.quote for each argument.

    ``cmd`` must be a simple command name (e.g., "bash", "test", "agentsh")
    matching ``[A-Za-z0-9._/-]+``. Raises ValueError otherwise.
    """
    if not cmd or not _CMD_RE.match(cmd):
        raise ValueError(f"cmd must be a simple command name without shell metacharacters: {cmd!r}")
    if not args:
        return cmd
    escaped = " ".join(shlex.quote(a) for a in args)
    return f"{cmd} {escaped}"


def fire_and_forget(coro: Coroutine[Any, Any, Any]) -> asyncio.Task[None]:
    """
    Schedule an async coroutine to run without awaiting it.
    Swallows exceptions to avoid noisy unhandled-task warnings.
    Used by adapters for detached/background process execution.

    Returns the task so adapters can optionally track and await
    pending background tasks during stop().
    """

    async def _wrapper() -> None:
        try:
            await coro
        except Exception:
            pass  # Detached processes — errors are expected and ignored

    return asyncio.create_task(_wrapper())
