from __future__ import annotations

import inspect
import re
from typing import Any


def normalize_text(text: str) -> str:
    text = re.sub(r"\s+", " ", (text or "")).strip()
    text = re.sub(r"\s+([,.;:!?…])", r"\1", text)
    return text.strip()


def _extract_text_from_content(content: Any) -> str:
    if content is None:
        return ""
    if isinstance(content, str):
        return content
    if isinstance(content, list):
        parts: list[str] = []
        for item in content:
            if isinstance(item, str):
                parts.append(item)
            else:
                text = getattr(item, "text", None)
                if isinstance(text, str):
                    parts.append(text)
                else:
                    rendered = str(item)
                    if rendered and rendered != "None":
                        parts.append(rendered)
        return "".join(parts).strip()
    text = getattr(content, "text", None)
    if isinstance(text, str):
        return text
    return str(content).strip()


def extract_spoken_from_speech_handle(handle: Any) -> str:
    items = getattr(handle, "chat_items", None)
    if not isinstance(items, list) or not items:
        return ""

    for item in reversed(items):
        role = getattr(item, "role", None)
        if role is None or str(role).lower() == "assistant":
            content = getattr(item, "content", None)
            spoken = normalize_text(_extract_text_from_content(content))
            if spoken:
                return spoken

    item = items[-1]
    return normalize_text(_extract_text_from_content(getattr(item, "content", None)))


async def _maybe_await(value: Any) -> Any:
    if inspect.isawaitable(value):
        return await value
    return value


async def _maybe_await_call(fn, *args, **kwargs) -> Any:
    if fn is None:
        return None
    try:
        value = fn(*args, **kwargs)
    except TypeError:
        value = fn(*args)
    return await _maybe_await(value)


class SpeechService:
    def __init__(self, session: Any) -> None:
        self._session = session

    async def start(
        self,
        text: str,
        *,
        allow_interruptions: bool,
        add_to_chat_ctx: bool = True,
        audio: Any = None,
    ) -> Any:
        kwargs = {
            "allow_interruptions": allow_interruptions,
            "add_to_chat_ctx": add_to_chat_ctx,
        }
        if audio is not None:
            kwargs["audio"] = audio
        value = self._session.say(text, **kwargs)
        if inspect.isawaitable(value) and not callable(getattr(value, "wait_for_playout", None)):
            return await value
        return value

    async def wait_for_playout(self, handle: Any) -> None:
        wait_for_playout = getattr(handle, "wait_for_playout", None)
        if callable(wait_for_playout):
            await _maybe_await_call(wait_for_playout)

    async def interrupt(self, handle: Any, *, force: bool = False) -> None:
        interrupt = getattr(handle, "interrupt", None)
        if callable(interrupt):
            await _maybe_await_call(interrupt, force=force)
            return

        session_interrupt = getattr(self._session, "interrupt", None)
        if callable(session_interrupt):
            await _maybe_await_call(session_interrupt, force=force)

    def extract_spoken_text(self, handle: Any) -> str:
        return extract_spoken_from_speech_handle(handle)
