"""
语音合成模块 (TTS)

使用 edge-tts (微软免费 TTS, 中文效果好)。
Windows 播放通过 MCI API (ctypes, 零依赖)。
"""

import asyncio
import ctypes
import queue
import subprocess
import platform
import tempfile
import threading
import os
import time
from pathlib import Path

VOICES = {
    "xiaoxiao": "zh-CN-XiaoxiaoNeural",    # 女声, 活泼清亮
    "yunxi": "zh-CN-YunxiNeural",          # 男声, 沉稳
    "xiaoyi": "zh-CN-XiaoyiNeural",        # 女声, 温柔
    "yunyang": "zh-CN-YunyangNeural",      # 男声, 新闻播报
    "yunjian": "zh-CN-YunjianNeural",      # 男声, 老成
}

SYSTEM = platform.system()


class TextToSpeech:

    def __init__(self, voice: str = "zh-CN-YunxiNeural", rate: str = "+0%"):
        self.voice = voice
        self.rate = rate

    def synthesize(self, text: str, output_path: str = None) -> str:
        """合成语音到 MP3，返回文件路径"""
        if not output_path:
            output_path = tempfile.mktemp(suffix=".mp3")

        async def _run():
            import edge_tts
            communicate = edge_tts.Communicate(text, self.voice, rate=self.rate)
            await communicate.save(output_path)

        asyncio.run(_run())
        return output_path

    def iter_audio(self, text: str):
        """边从 edge-tts 收音频边产出 MP3 数据块。"""
        chunks: queue.Queue[object] = queue.Queue()
        finished = object()

        def produce() -> None:
            try:
                async def _run():
                    import edge_tts

                    communicate = edge_tts.Communicate(text, self.voice, rate=self.rate)
                    async for message in communicate.stream():
                        if message.get("type") != "audio":
                            continue
                        data = message.get("data")
                        if data:
                            chunks.put(data)

                asyncio.run(_run())
            except BaseException as exc:
                chunks.put(exc)
            finally:
                chunks.put(finished)

        threading.Thread(target=produce, name="coco-tts-stream", daemon=True).start()
        while True:
            item = chunks.get()
            if item is finished:
                return
            if isinstance(item, BaseException):
                raise item
            yield item

    def speak(self, text: str, block: bool = True) -> str:
        """合成并播放"""
        self._stopped = False
        path = self.synthesize(text)

        if not self._stopped:
            if block:
                self._play_blocking(path)
            else:
                self._play_async(path)

        return path

    def stop(self):
        """打断当前播放"""
        self._stopped = True

    def _play_blocking(self, audio_path: str):
        """阻塞播放（可被 stop() 打断）"""
        if SYSTEM == "Windows":
            _play_mci_interruptible(audio_path, self)
        elif SYSTEM == "Darwin":
            subprocess.run(["afplay", audio_path], capture_output=True, timeout=30)
        else:
            subprocess.run(["aplay", audio_path], capture_output=True, timeout=30)

    @staticmethod
    def _play_async(audio_path: str):
        if SYSTEM == "Windows":
            _play_mci_async(audio_path)
        elif SYSTEM == "Darwin":
            subprocess.Popen(["afplay", audio_path])
        else:
            subprocess.Popen(["aplay", audio_path])


def _play_mci_interruptible(filepath: str, tts_obj=None):
    """
    通过 Windows MCI 播放 MP3，可被 tts_obj.stop() 打断。
    """
    alias = f"coco_{hash(filepath) & 0x7FFFFFFF:08x}"
    path = os.path.abspath(filepath).replace("/", "\\")
    buf = ctypes.create_unicode_buffer(256)

    # 打开
    ctypes.windll.winmm.mciSendStringW(
        f'open "{path}" type mpegvideo alias {alias}', buf, 255, 0)

    # 播放（不等待，异步播放）
    ctypes.windll.winmm.mciSendStringW(f"play {alias}", buf, 255, 0)

    # 轮询等待播放结束或被打断
    try:
        while True:
            time.sleep(0.1)
            ctypes.windll.winmm.mciSendStringW(
                f"status {alias} mode", buf, 255, 0)
            if "stopped" in buf.value.lower():
                break
            if tts_obj and tts_obj._stopped:
                ctypes.windll.winmm.mciSendStringW(
                    f"stop {alias}", buf, 255, 0)
                break
    finally:
        ctypes.windll.winmm.mciSendStringW(f"close {alias}", buf, 255, 0)

    # 重置 stopped 标志
    if tts_obj:
        tts_obj._stopped = False


def _play_mci_async(filepath: str):
    """非阻塞 MCI 播放（异步，不等待）"""
    alias = f"coco_{hash(filepath) & 0x7FFFFFFF:08x}"
    path = os.path.abspath(filepath).replace("/", "\\")
    buf = ctypes.create_unicode_buffer(256)
    ctypes.windll.winmm.mciSendStringW(
        f'open "{path}" type mpegvideo alias {alias}', buf, 255, 0)
    ctypes.windll.winmm.mciSendStringW(f"play {alias}", buf, 255, 0)

    def _finish():
        time.sleep(0.5)
        while True:
            time.sleep(0.1)
            ctypes.windll.winmm.mciSendStringW(
                f"status {alias} mode", buf, 255, 0)
            if "stopped" in buf.value.lower():
                break
        ctypes.windll.winmm.mciSendStringW(f"close {alias}", buf, 255, 0)

    import threading
    threading.Thread(target=_finish, daemon=True).start()
