"""AudioMic Windows current-cursor helper (stdlib only).

Paste the temporary pairing string shown by the computer webpage. Final
transcripts are polled over HTTPS and typed into the current foreground app.
Nothing is written to disk.
"""

import ctypes
import json
import os
import queue
import subprocess
import sys
import threading
import time
import tkinter as tk
from tkinter import messagebox, ttk
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode, urlparse
from urllib.request import Request, urlopen


KEYEVENTF_KEYUP = 0x0002
KEYEVENTF_UNICODE = 0x0004
INPUT_KEYBOARD = 1
GA_ROOT = 2
CSIDL_DESKTOPDIRECTORY = 0x0010


def application_path():
    if getattr(sys, "frozen", False):
        return os.path.abspath(sys.executable)
    return os.path.abspath(__file__)


def desktop_directory():
    if not hasattr(ctypes, "windll"):
        raise RuntimeError("创建桌面快捷方式仅支持 Windows")
    buffer = ctypes.create_unicode_buffer(32768)
    result = ctypes.windll.shell32.SHGetFolderPathW(
        None, CSIDL_DESKTOPDIRECTORY, None, 0, buffer
    )
    if result != 0 or not buffer.value:
        raise RuntimeError("无法找到当前用户桌面目录")
    return buffer.value


def create_desktop_shortcut():
    target = application_path()
    link = os.path.join(desktop_directory(), "AudioMic 当前光标助手.lnk")
    script = (
        "$shell=New-Object -ComObject WScript.Shell;"
        "$shortcut=$shell.CreateShortcut($args[1]);"
        "$shortcut.TargetPath=$args[0];"
        "$shortcut.WorkingDirectory=[IO.Path]::GetDirectoryName($args[0]);"
        "$shortcut.Description='AudioMic 当前光标助手';"
        "$shortcut.Save()"
    )
    completed = subprocess.run(
        [
            "powershell.exe",
            "-NoProfile",
            "-NonInteractive",
            "-Command",
            script,
            target,
            link,
        ],
        capture_output=True,
        text=True,
        timeout=15,
        creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
    )
    if completed.returncode != 0 or not os.path.exists(link):
        raise RuntimeError("Windows 未能创建快捷方式")
    return link


def parse_pairing(value):
    parts = [item.strip() for item in value.strip().split("|")]
    if len(parts) != 3:
        raise ValueError("连接串格式不正确")
    origin, token, receiver_key = parts
    parsed = urlparse(origin)
    if parsed.scheme != "https" or not parsed.netloc or parsed.path not in ("", "/"):
        raise ValueError("连接地址必须是 HTTPS 网站根地址")
    if not (token.isdigit() and len(token) == 6):
        raise ValueError("令牌必须是 6 位数字")
    if len(receiver_key) < 32:
        raise ValueError("临时接收密钥不完整")
    return origin.rstrip("/"), token, receiver_key


def utf16_units(text):
    raw = text.encode("utf-16-le")
    return [raw[index] | (raw[index + 1] << 8) for index in range(0, len(raw), 2)]


if hasattr(ctypes, "windll"):
    ULONG_PTR = ctypes.c_size_t

    class KEYBDINPUT(ctypes.Structure):
        _fields_ = [
            ("wVk", ctypes.c_ushort),
            ("wScan", ctypes.c_ushort),
            ("dwFlags", ctypes.c_ulong),
            ("time", ctypes.c_ulong),
            ("dwExtraInfo", ULONG_PTR),
        ]

    class MOUSEINPUT(ctypes.Structure):
        _fields_ = [
            ("dx", ctypes.c_long),
            ("dy", ctypes.c_long),
            ("mouseData", ctypes.c_ulong),
            ("dwFlags", ctypes.c_ulong),
            ("time", ctypes.c_ulong),
            ("dwExtraInfo", ULONG_PTR),
        ]

    class HARDWAREINPUT(ctypes.Structure):
        _fields_ = [
            ("uMsg", ctypes.c_ulong),
            ("wParamL", ctypes.c_ushort),
            ("wParamH", ctypes.c_ushort),
        ]

    class INPUT_UNION(ctypes.Union):
        _fields_ = [
            ("mi", MOUSEINPUT),
            ("ki", KEYBDINPUT),
            ("hi", HARDWAREINPUT),
        ]

    class INPUT(ctypes.Structure):
        _anonymous_ = ("data",)
        _fields_ = [("type", ctypes.c_ulong), ("data", INPUT_UNION)]


def type_unicode(text):
    if not hasattr(ctypes, "windll"):
        raise RuntimeError("当前光标输入仅支持 Windows")
    for unit in utf16_units(text):
        events = [
            INPUT(
                type=INPUT_KEYBOARD,
                ki=KEYBDINPUT(0, unit, KEYEVENTF_UNICODE, 0, 0),
            ),
            INPUT(
                type=INPUT_KEYBOARD,
                ki=KEYBDINPUT(
                    0, unit, KEYEVENTF_UNICODE | KEYEVENTF_KEYUP, 0, 0
                ),
            ),
        ]
        array_type = INPUT * 2
        sent = ctypes.windll.user32.SendInput(
            2, array_type(*events), ctypes.sizeof(INPUT)
        )
        if sent != 2:
            raise RuntimeError("Windows 未能完成文字输入")
        time.sleep(0.001)


class AudioMicHelper:
    def __init__(self, root):
        self.root = root
        self.root.title("AudioMic 当前光标助手")
        self.root.geometry("650x320")
        self.root.minsize(520, 300)
        self.stop_event = threading.Event()
        self.worker = None
        self.messages = queue.Queue()
        self.cursor = 0

        frame = ttk.Frame(root, padding=18)
        frame.pack(fill="both", expand=True)
        ttk.Label(
            frame,
            text="AudioMic 当前光标助手",
            font=("Microsoft YaHei UI", 15, "bold"),
        ).pack(anchor="w")
        ttk.Label(
            frame,
            text="粘贴电脑网页显示的临时连接串，连接后点击要输入文字的软件和光标位置。",
            wraplength=570,
        ).pack(anchor="w", pady=(6, 12))
        self.pairing = ttk.Entry(frame)
        self.pairing.pack(fill="x")

        row = ttk.Frame(frame)
        row.pack(fill="x", pady=12)
        self.start_button = ttk.Button(row, text="开始接收", command=self.toggle)
        self.start_button.pack(side="left")
        ttk.Button(row, text="退出", command=self.close).pack(side="left", padx=8)
        self.status = ttk.Label(row, text="尚未连接")
        self.status.pack(side="left", padx=12)

        install_row = ttk.Frame(frame)
        install_row.pack(fill="x", pady=(0, 10))
        ttk.Button(
            install_row,
            text="创建桌面快捷方式",
            command=self.add_desktop_shortcut,
        ).pack(side="left")
        ttk.Button(
            install_row,
            text="固定到任务栏",
            command=self.show_taskbar_instructions,
        ).pack(side="left", padx=8)

        ttk.Label(
            frame,
            text=(
                "安全说明：连接串仅在本次 4 小时配对内有效；助手不保存语音、"
                "文字或密钥。连接后请不要把助手窗口保持为当前输入焦点。"
            ),
            wraplength=570,
            foreground="#666",
        ).pack(anchor="w", pady=(4, 0))
        self.root.after(100, self.drain_messages)
        self.root.protocol("WM_DELETE_WINDOW", self.close)

    def add_desktop_shortcut(self):
        try:
            path = create_desktop_shortcut()
            self.status.config(text="桌面快捷方式已创建")
            messagebox.showinfo("AudioMic", "已创建：\n" + path)
        except (RuntimeError, OSError, subprocess.SubprocessError) as error:
            self.status.config(text=str(error))
            messagebox.showerror("AudioMic", str(error))

    def show_taskbar_instructions(self):
        messagebox.showinfo(
            "固定到任务栏",
            "Windows 不允许程序静默固定任务栏。\n\n"
            "请在助手运行时，右键任务栏上的 AudioMic 图标，"
            "再选择“固定到任务栏”。",
        )
        self.root.iconify()

    def toggle(self):
        if self.worker and self.worker.is_alive():
            self.stop_event.set()
            self.status.config(text="正在停止…")
            return
        try:
            connection = parse_pairing(self.pairing.get())
        except ValueError as error:
            self.status.config(text=str(error))
            return
        self.stop_event.clear()
        self.cursor = 0
        self.pairing.config(state="disabled")
        self.start_button.config(text="停止接收")
        self.status.config(text="正在连接…")
        self.worker = threading.Thread(
            target=self.poll, args=connection, daemon=True
        )
        self.worker.start()

    def poll(self, origin, token, receiver_key):
        failures = 0
        while not self.stop_event.is_set():
            query = urlencode(
                {
                    "token": token,
                    "after": self.cursor,
                }
            )
            request = Request(
                origin + "/api/audiomic/native/events?" + query,
                headers={
                    "Accept": "application/json",
                    "Authorization": "Bearer " + receiver_key,
                },
            )
            try:
                with urlopen(request, timeout=12) as response:
                    payload = json.loads(response.read().decode("utf-8"))
                failures = 0
                self.messages.put(("status", "已连接；请点击目标软件的光标位置"))
                for event in payload.get("events", []):
                    self.cursor = max(self.cursor, int(event.get("seq", 0)))
                    transcript = event.get("payload") or {}
                    if transcript.get("final") and transcript.get("text"):
                        self.messages.put(("type", str(transcript["text"])))
                self.stop_event.wait(0.35)
            except HTTPError as error:
                if error.code in (404, 410):
                    self.messages.put(("stopped", "配对已失效，请在网页重新生成"))
                    return
                failures += 1
                self.messages.put(("status", "服务器暂时不可用，正在重试…"))
            except (URLError, OSError, ValueError, json.JSONDecodeError):
                failures += 1
                self.messages.put(("status", "网络中断，正在重试…"))
            if failures:
                self.stop_event.wait(min(5.0, 0.5 * failures))
        self.messages.put(("stopped", "已停止"))

    def helper_has_focus(self):
        own = ctypes.windll.user32.GetAncestor(self.root.winfo_id(), GA_ROOT)
        return ctypes.windll.user32.GetForegroundWindow() == own

    def drain_messages(self):
        try:
            while True:
                action, value = self.messages.get_nowait()
                if action == "status":
                    self.status.config(text=value)
                elif action == "type":
                    if self.helper_has_focus():
                        self.status.config(text="请先点击目标软件的光标，再继续说话")
                    else:
                        try:
                            type_unicode(value)
                            self.status.config(text="已输入：" + value[:28])
                        except RuntimeError as error:
                            self.status.config(text=str(error))
                elif action == "stopped":
                    self.status.config(text=value)
                    self.start_button.config(text="开始接收")
                    self.pairing.config(state="normal")
        except queue.Empty:
            pass
        self.root.after(100, self.drain_messages)

    def close(self):
        self.stop_event.set()
        self.root.destroy()


def main():
    if not hasattr(ctypes, "windll"):
        raise SystemExit("AudioMic 当前光标助手仅支持 Windows")
    root = tk.Tk()
    AudioMicHelper(root)
    root.mainloop()


if __name__ == "__main__":
    main()
