"""Tkinter GUI：主线程事件循环 + 后台线程内 asyncio 与 ``RuntimeContext``（Win/Linux，标准库）。"""

from __future__ import annotations

import asyncio
import queue
import threading
from pathlib import Path
from typing import Any

import typer

from aicd.runtime import build_runtime
from aicd.setup_wizard import runtime_llm_ready, should_prompt_setup
from aicd.ui.cli_headless import one_chat_turn, shutdown_ctx_after_cli


def _log_put(log_q: queue.Queue[tuple[str, Any]], msg: str) -> None:
    log_q.put(("log", msg))


def run_gui_app(
    *,
    home: Path | None,
    work: Path | None,
    out: Path | None,
    skip_setup: bool,
    bootstrap_ai: str | None,
) -> None:
    try:
        import tkinter as tk
        from tkinter import filedialog, messagebox, scrolledtext
    except ImportError as e:
        typer.echo(
            "无法加载 tkinter（Linux 通常需: sudo apt install python3-tk）。",
            err=True,
        )
        raise typer.Exit(code=1) from e

    log_q: queue.Queue[tuple[str, Any]] = queue.Queue()
    cmd_q: queue.Queue[tuple[str, Any]] = queue.Queue()
    stop = threading.Event()
    worker_exc: list[BaseException] = []
    ctx_holder: list[Any] = [None]

    def worker() -> None:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        ctx: RuntimeContext | None = None
        try:
            ctx = loop.run_until_complete(
                build_runtime(
                    home=home,
                    skip_first_setup=skip_setup,
                    setup_tty_relaxed=True,
                )
            )
            ctx_holder[0] = ctx  # 供 UI 刷新工作区显示
            if (
                not skip_setup
                and not runtime_llm_ready(ctx.config)
                and not should_prompt_setup(relaxed=True)
            ):
                _log_put(
                    log_q,
                    "[aicd] 未检测到可交互终端向导；请先在终端执行: aicd init\n"
                    "或设置 OPENAI_API_KEY / 编辑 config.yaml 中的 llm.apiKey。",
                )
            if work is not None:
                ok, msg = ctx.set_ai_workspace(str(work))
                if not ok:
                    _log_put(log_q, f"[aicd] 工作区设置失败: {msg}")
            if out is not None:
                ok, msg = ctx.set_ai_output_dir(str(out))
                if not ok:
                    _log_put(log_q, f"[aicd] 输出目录设置失败: {msg}")

            first = (bootstrap_ai or "").strip()
            if first:

                def emit_boot(s: str) -> None:
                    _log_put(log_q, s)

                async def _boot() -> None:
                    code, reply = await one_chat_turn(ctx, first, emit=emit_boot)
                    if reply is not None:
                        _log_put(log_q, f"\n--- assistant ---\n{reply}\n")
                    log_q.put(("turn_done", code))

                loop.run_until_complete(_boot())

            log_q.put(("ready", True))

            while not stop.is_set():
                try:
                    kind, payload = cmd_q.get(timeout=0.15)
                except queue.Empty:
                    loop.run_until_complete(asyncio.sleep(0))
                    continue
                if kind == "shutdown":
                    break
                if kind == "set_work" and isinstance(payload, str) and ctx is not None:
                    ok, msg = ctx.set_ai_workspace(payload)
                    if ok:
                        _log_put(log_q, f"[aicd] 工作区: {ctx.workspace_cwd}")
                    else:
                        _log_put(log_q, f"[aicd] 工作区失败: {msg}")
                    log_q.put(("turn_done", 0))
                    continue
                if kind == "chat" and isinstance(payload, str) and ctx is not None:

                    def emit_chat(s: str) -> None:
                        _log_put(log_q, s)

                    async def _go() -> None:
                        code, reply = await one_chat_turn(ctx, payload, emit=emit_chat)
                        if reply is not None:
                            _log_put(log_q, f"\n--- assistant ---\n{reply}\n")
                        log_q.put(("turn_done", code))

                    loop.run_until_complete(_go())
        except BaseException as e:
            worker_exc.append(e)
            log_q.put(("log", f"[aicd] 后台错误: {e}"))
        finally:
            if ctx is not None:
                try:
                    loop.run_until_complete(shutdown_ctx_after_cli(ctx))
                except Exception:
                    pass
            try:
                loop.close()
            except Exception:
                pass
            log_q.put(("worker_stopped", None))

    t = threading.Thread(target=worker, name="aicd-gui-async", daemon=True)
    t.start()

    root = tk.Tk()
    root.title("Aicd")
    root.minsize(520, 400)

    workspace_var = tk.StringVar(value="（启动后设置）")
    send_btn = tk.Button(root, text="发送", state=tk.DISABLED)

    top = tk.Frame(root)
    top.pack(fill=tk.X, padx=6, pady=4)
    tk.Label(top, text="AI workspace:").pack(side=tk.LEFT)
    tk.Label(top, textvariable=workspace_var, anchor="w").pack(
        side=tk.LEFT, fill=tk.X, expand=True, padx=6
    )

    def browse_work() -> None:
        d = filedialog.askdirectory(title="选择 AI 工作区")
        if d:
            cmd_q.put(("set_work", d))
            send_btn.config(state=tk.DISABLED)

    tk.Button(top, text="浏览…", command=browse_work).pack(side=tk.RIGHT)

    log = scrolledtext.ScrolledText(root, wrap=tk.WORD, state=tk.DISABLED, height=20)
    log.pack(fill=tk.BOTH, expand=True, padx=6, pady=4)

    bottom = tk.Frame(root)
    bottom.pack(fill=tk.BOTH, padx=6, pady=4)
    inp = tk.Text(bottom, wrap=tk.WORD, height=5)
    inp.pack(fill=tk.BOTH, expand=True)

    def append_log(s: str) -> None:
        log.config(state=tk.NORMAL)
        log.insert(tk.END, s + "\n")
        log.see(tk.END)
        log.config(state=tk.DISABLED)

    def on_turn_done() -> None:
        send_btn.config(state=tk.NORMAL)
        try:
            c = ctx_holder[0]
            if c is not None:
                workspace_var.set(str(c.workspace_cwd))
        except Exception:
            pass

    def send_message() -> None:
        raw = inp.get("1.0", tk.END).strip()
        if not raw:
            return
        append_log(f"--- you ---\n{raw}\n")
        inp.delete("1.0", tk.END)
        send_btn.config(state=tk.DISABLED)
        cmd_q.put(("chat", raw))

    send_btn.config(command=send_message)
    send_btn.pack(fill=tk.X, padx=6, pady=(0, 6))

    err_shown = [False]

    def poll_log() -> None:
        try:
            while True:
                typ, data = log_q.get_nowait()
                if typ == "log":
                    append_log(str(data))
                elif typ == "ready":
                    send_btn.config(state=tk.NORMAL)
                elif typ == "turn_done":
                    on_turn_done()
                elif typ == "worker_stopped":
                    pass
        except queue.Empty:
            pass
        if worker_exc and not err_shown[0]:
            err_shown[0] = True
            try:
                messagebox.showerror("Aicd", str(worker_exc[0]))
            except Exception:
                pass
        root.after(80, poll_log)

    def on_close() -> None:
        stop.set()
        cmd_q.put(("shutdown", None))
        root.destroy()

    root.protocol("WM_DELETE_WINDOW", on_close)
    root.after(100, poll_log)
    root.mainloop()
