"""以 / 开头的 TUI 本地命令（不发给 LLM）。"""

from __future__ import annotations

import os
import shlex
from dataclasses import dataclass
from subprocess import list2cmdline


@dataclass(frozen=True)
class SlashCommandDef:
    name: str
    usage: str
    description: str


SLASH_REGISTRY: list[SlashCommandDef] = [
    SlashCommandDef(
        "help",
        "/?  或  /help",
        "列出所有 / 命令说明",
    ),
    SlashCommandDef(
        "work",
        "/work [path]",
        "设置主 AI 的工作目录（相对路径基于当前工作目录解析）；"
        "不配 path 时显示当前工作目录。与 AICD_HOME（配置/任务/缓存）无关。",
    ),
    SlashCommandDef(
        "doc",
        "/doc [path|default]",
        "AI 输出根目录（默认 workspace/aicd，内含 tmp/ 作中间数据）。"
        "无参显示当前；/doc <path> 自定义；/doc default 恢复默认。",
    ),
    SlashCommandDef(
        "llm",
        "/llm  或  /llm <键> <值>",
        "查看或写入 LLM 采样参数（写入 config.yaml）：temperature、topP、frequencyPenalty、"
        "presencePenalty、maxOutputTokens、repetitionPenalty、model、timeout、maxRetries、retryBaseDelaySec 等。",
    ),
    SlashCommandDef(
        "agent",
        "/agent  或  /agent rounds <n>  |  /agent subRounds <n>  |  /agent heartbeat <秒>  |  /agent subHeartbeat <秒>",
        "主对话 **heartbeat**：空闲时按间隔唤醒主 AI 检查 **task_list**（5–3600 秒，0=关）。**subHeartbeat**：子 Agent 在仍有 running 子任务且收件箱空时睡眠再拉收件箱或注入协调心跳。rounds/subRounds 仍为 1–500。",
    ),
    SlashCommandDef(
        "exit",
        "/exit  或  /quit  或  /q",
        "安全退出：停止当前聊天、取消所有后台任务（含 shell/子 Agent）、关闭数据库与日志；"
        "与 Ctrl+Q 走相同清理流程。",
    ),
    SlashCommandDef(
        "chat",
        "/chat stop  |  /chat help",
        "仅控制**主对话**：**stop**=中断当前主 AI 轮次并丢弃已排队的主对话请求（**不**删任务库、**不**停后台 task_ai/task_shell）。"
        "与 **/task stop**、Ctrl+Shift+C 等价。",
    ),
    SlashCommandDef(
        "task",
        "/task help | /task stop | /task list | /task clear | /task ai … | /task shell … | /task dummy [steps]",
        "不经过主对话 LLM，直接在后台排队："
        "**ai**=子 Agent（复杂多步）；**shell**=本机命令（下载/解压/搜索等，日志在右侧）；"
        "**stop**=仅中断当前主对话轮次并清空主对话队列（同 **/chat stop**）；"
        "**list**=刷新任务表；**clear**=清空任务历史并中断主 AI、丢弃排队主对话（库表+右侧+HOME/tasks 与 tmp/tasks）。",
    ),
    SlashCommandDef(
        "history",
        "/history list  |  /history clear  |  /history <N>  |  /history copy <N>  |  /history help",
        "左侧输出区不能像普通终端那样鼠标拖选；/history N 会打印全文并尝试写入**系统剪贴板**，/history copy N 仅剪贴板不刷屏。"
        "list/N 仅含非 / 提示词；见 data/tui_input_history.jsonl。**list**=编号清单；**clear**=清空并删文件记录；"
        "**N**=在左侧输出区**打印第 N 条全文**便于复制。",
    ),
]


def join_args_for_shell(parts: list[str]) -> str:
    """把 shlex 拆开后的参数拼回一条可交给 ``task_shell`` 的命令行（保留引号语义）。"""
    if not parts:
        return ""
    if os.name == "posix":
        return shlex.join(parts)
    return list2cmdline(parts)


def chat_slash_help_text() -> str:
    return (
        "[/chat] 主对话控制（**不**清空任务库、**不**取消后台 task）\n"
        "  /chat stop    中断当前主 AI 回复轮次，并丢弃已排队的主对话请求（等同 **/task stop**、Ctrl+Shift+C）\n"
        "  /chat help    本说明\n"
        "\n"
        "处理中时请看**顶部标题栏**（等待大模型 / 正在执行的工具名）；长时间无新日志多为单次工具或请求较慢。\n"
        "若还要清空右侧任务历史，请用 **/task clear**。"
    )


def task_slash_help_text() -> str:
    return (
        "[/task] 后台任务（不发给主对话模型）\n"
        "  /task stop           **仅**中断当前**主对话**轮次并丢弃已排队主消息（与 **/chat stop**、Ctrl+Shift+C 相同；"
        "**不**清空任务库、**不**停后台 task_ai/task_shell）\n"
        "  /task ai <说明…>     启动子 Agent，适合复杂、多轮工具任务\n"
        "  /task shell <命令…>  启动后台 Shell（curl 下载、tar/zip 解压、rg/find 搜索等）\n"
        "  /task list           刷新右侧任务表\n"
        "  /task clear          清空全部任务历史，并**中断当前主 AI 轮次**、丢弃已排队的主对话请求，"
        "避免清空后仍按旧指令继续 task_ai；另：取消运行中任务、删库表、清理 HOME/tasks 与 tmp/tasks\n"
        "  /task dummy [n]      测试用假进度任务（默认 5 步）\n"
        "  /task help           本说明\n"
        "\n"
        "主对话里的 AI 仍可用工具 **task_ai** / **task_shell** 替你排队；"
        "查看状态用 **task_list**、**task_result** 或看右侧面板。"
        "右侧列表会**保留最近已完成的**任务（不占位阻塞）；新子任务只有在本轮对话里**实际调用了** **task_ai**（或你用 **/task ai**）时才会多出一行。"
        "若要由 AI 清空历史，可说「清空任务历史」，模型应调用 **task_history_clear**（**confirm**: true）。"
    )


def slash_help_text() -> str:
    lines = ["[/] 命令：", ""]
    for d in SLASH_REGISTRY:
        lines.append(f"  {d.usage}")
        lines.append(f"    {d.description}")
        lines.append("")
    lines.append(
        "HOME（AICD_HOME）：保存 config、SQLite、tasks 历史、cache 等程序自身数据。"
        " 任务记录持久化，重启后仍在；可用 **/task clear** 或主对话 **task_history_clear**（confirm=true）清空。"
    )
    return "\n".join(lines).rstrip()


def parse_slash_line(line: str) -> tuple[str, list[str]] | None:
    """解析一行 / 命令，返回 (主命令名不含斜杠, 参数列表)。非 / 开头返回 None。"""
    s = line.strip()
    if not s.startswith("/"):
        return None
    body = s[1:].strip()
    if not body:
        return "help", []
    try:
        parts = shlex.split(body, posix=os.name == "posix")
    except ValueError:
        parts = body.split(None, 1)
        parts = [parts[0], parts[1]] if len(parts) > 1 else [parts[0]]
    if not parts:
        return "help", []
    cmd = parts[0].lower()
    args = parts[1:]
    if cmd in ("?", "help", "h"):
        cmd = "help"
    return cmd, args
