"""在指定工作区跑一轮主会话 ``agent.chat``（用于 Host / 插件多工作区并行）。"""

from __future__ import annotations

import json
import re
import uuid
from pathlib import Path
from typing import Any

from aicd.runtime import RuntimeContext, build_runtime
from aicd.tui_prefs import parse_main_chat_mode_token
from aicd.ui.cli_headless import one_chat_turn, shutdown_ctx_after_cli

_PLUGIN_ID_RE = re.compile(r"^[a-z][a-z0-9_-]{0,63}$")


def allocate_plugin_chat_workspace(layout: dict[str, Path], plugin_id: str) -> Path:
    """在 ``HOME/plugins/<plugin_id>/_aicd_chat_spaces/<uuid>/`` 创建空目录并返回。"""

    pid = plugin_id.strip()
    if pid in ("", "_template") or not _PLUGIN_ID_RE.match(pid):
        raise ValueError(f"invalid plugin_id: {plugin_id!r}")
    plugins_root = layout["plugins"].resolve()
    root = (plugins_root / pid / "_aicd_chat_spaces").resolve()
    try:
        root.relative_to(plugins_root)
    except ValueError as e:
        raise ValueError("invalid plugins layout") from e
    root.mkdir(parents=True, exist_ok=True)
    d = root / str(uuid.uuid4())
    d.mkdir(parents=False)
    return d


def resolve_host_chat_workspace(
    ctx: RuntimeContext,
    params: dict[str, Any],
) -> tuple[Path | None, str | None]:
    """解析 Host ``main_chat_workspace`` 的工作区：显式路径或在插件下分配 UUID 目录。"""

    alloc = bool(
        params.get("allocate_workspace") or params.get("allocate_under_plugin")
    )
    pid = str(params.get("plugin_id") or "").strip()
    if alloc:
        if not pid:
            return None, "plugin_id required when allocate_workspace is true"
        try:
            return allocate_plugin_chat_workspace(ctx.layout, pid), None
        except ValueError as e:
            return None, str(e)
    ws = params.get("workspace")
    if isinstance(ws, str) and ws.strip():
        p = Path(ws.strip()).expanduser().resolve()
        try:
            ctx.router.path_policy.check_allowed(p)
        except PermissionError as e:
            return None, str(e)
        if not p.is_dir():
            return None, f"not a directory: {p}"
        return p, None
    return None, "provide workspace (string path) or allocate_workspace=true and plugin_id"


async def run_workspace_chat_once(
    *,
    home: Path | None,
    workspace: Path,
    user_message: str,
    session_id: str | None = None,
    skip_first_setup: bool = True,
    cli_no_plugins: bool = False,
    plugin_policy_override: str | None = None,
    main_chat_mode: str | None = None,
) -> dict[str, Any]:
    """构建 Runtime → 切换 AI 工作区 → 一轮 ``chat`` → 关闭。返回可 JSON 序列化的 dict。"""

    ws = workspace.expanduser().resolve()
    if not ws.is_dir():
        return {
            "ok": False,
            "error": f"not a directory: {ws}",
            "workspace": str(ws),
        }
    msg = (user_message or "").strip()
    if not msg:
        return {"ok": False, "error": "empty message", "workspace": str(ws)}

    override: str | None = None
    if main_chat_mode and str(main_chat_mode).strip():
        canon, err = parse_main_chat_mode_token(str(main_chat_mode).strip())
        if canon is None:
            return {
                "ok": False,
                "error": err or "invalid main_chat_mode",
                "workspace": str(ws),
            }
        override = canon

    ctx: RuntimeContext | None = None
    try:
        ctx = await build_runtime(
            home=home,
            session_id=session_id,
            skip_first_setup=skip_first_setup,
            setup_tty_relaxed=True,
            cli_no_plugins=cli_no_plugins,
            plugin_policy_override=plugin_policy_override,
        )
        ok_ws, wmsg = ctx.set_ai_workspace(str(ws))
        if not ok_ws:
            return {
                "ok": False,
                "error": wmsg,
                "workspace": str(ws),
            }

        tool_lines: list[str] = []

        def _emit(line: str) -> None:
            tool_lines.append(line)

        code, reply = await one_chat_turn(
            ctx,
            msg,
            emit=_emit,
            main_chat_mode_override=override,
        )
        sid = getattr(ctx.router, "_chat_session_id", None)
        out: dict[str, Any] = {
            "ok": code == 0,
            "reply": reply,
            "tool_lines": tool_lines,
            "workspace": str(ws),
            "session_id": sid,
        }
        if code != 0:
            out["error"] = "chat_turn_failed"
        return out
    finally:
        if ctx is not None:
            await shutdown_ctx_after_cli(ctx)


def chat_once_result_json(result: dict[str, Any]) -> str:
    return json.dumps(result, ensure_ascii=False) + "\n"
