"""Animation utilities for CLI."""
from __future__ import annotations

import asyncio
import sys
import threading
import time
from collections.abc import Coroutine
from typing import Any, TypeVar

import click


T = TypeVar("T")


def thinking_animation(stop_event: threading.Event, message: str = "Thinking") -> None:
    """Display thinking animation in terminal.
    
    Args:
        stop_event: Event to signal stop
        message: Message to display
    """
    frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
    idx = 0
    
    while not stop_event.is_set():
        click.echo(f"\r{message} {frames[idx % len(frames)]}", nl=False)
        idx += 1
        time.sleep(0.1)
    
    click.echo("\r" + " " * (len(message) + 3) + "\r", nl=False)


async def run_with_animation(
    coro: Coroutine[Any, Any, T],
    message: str = "Thinking",
) -> T:
    """Run coroutine with thinking animation.
    
    Args:
        coro: Coroutine to run
        message: Animation message
        
    Returns:
        Result of coroutine
    """
    stop_event = threading.Event()
    
    def animate() -> None:
        frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
        idx = 0
        while not stop_event.is_set():
            sys.stdout.write(f"\r{message} {frames[idx % len(frames)]} ")
            sys.stdout.flush()
            idx += 1
            time.sleep(0.08)
        sys.stdout.write("\r" + " " * (len(message) + 3) + "\r")
        sys.stdout.flush()
    
    task: asyncio.Task[T] = asyncio.create_task(coro)
    animate_task = asyncio.create_task(asyncio.to_thread(animate))
    
    try:
        result = await task
    finally:
        stop_event.set()
        await animate_task
    
    return result


def run_sync_coro(coro: Coroutine[Any, Any, T]) -> T:
    """Run a coroutine from sync code, even if an event loop is already active.
    
    Args:
        coro: Coroutine to run
        
    Returns:
        Result of coroutine
    """
    try:
        asyncio.get_running_loop()
    except RuntimeError:
        return asyncio.run(coro)
    
    result: dict[str, T] = {}
    error: dict[str, BaseException] = {}
    
    def _runner() -> None:
        try:
            result["value"] = asyncio.run(coro)
        except BaseException as exc:
            error["value"] = exc
    
    thread = threading.Thread(target=_runner, daemon=True)
    thread.start()
    thread.join()
    
    if "value" in error:
        raise error["value"]
    
    return result["value"]


__all__ = [
    "thinking_animation",
    "run_with_animation",
    "run_sync_coro",
]
