"use client";

import {
  Bot,
  Boxes,
  Brain,
  Check,
  Copy,
  Database,
  Eye,
  EyeOff,
  MessageSquareText,
  MoreHorizontal,
  Pencil,
  Plus,
  Rocket,
  Sparkles,
  Trash2,
  Workflow,
  ChevronLeft,
  ChevronUp,
  ChevronDown,
  Save,
  Mic,
  Send,
  HelpCircle,
  Waypoints,
  AudioLines,
  Terminal,
  Loader2,
  PhoneOff,
  Orbit,
  Waves,
} from "lucide-react";

import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Switch } from "@/components/ui/switch";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import {
  Popover,
  PopoverContent,
  PopoverTrigger,
} from "@/components/ui/popover";
import { AuraVisualizer } from "@/components/ui/aura-visualizer";
import { NebulaVisualizer } from "@/components/ui/nebula-visualizer";
import { SpectrumVisualizer } from "@/components/ui/spectrum-visualizer";
import { WaveVisualizer } from "@/components/ui/wave-visualizer";
import { WaveformTimelinePanel } from "@/components/ui/waveform-timeline";
import { DataList } from "@/components/ui/data-list";
import { PageHeader } from "@/components/ui/page-header";
import { FilterPills } from "@/components/ui/filter-pills";
import { SearchInput } from "@/components/ui/search-input";
import { ListToolbar } from "@/components/ui/list-toolbar";
import {
  Card,
  CardContent,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import { useCallback, useEffect, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import {
  assistantsApi,
  knowledgeBasesApi,
  modelResourcesApi,
  type Assistant,
  type AssistantType as ApiAssistantType,
  type AssistantUpsert,
  type KnowledgeBase,
  type ModelResource,
} from "@/lib/api";
import {
  useVoicePreview,
  type ChatMessage,
  type VoicePreviewStatus,
} from "@/hooks/use-voice-preview";

type RuntimeMode = "pipeline" | "realtime";

type AssistantForm = {
  name: string;
  greeting: string;
  prompt: string;
  runtimeMode: RuntimeMode;
  realtimeModel: string;
  model: string;
  asr: string;
  voice: string;
  knowledgeBase: string;
  enableInterrupt: boolean;
};

type FastGptForm = {
  name: string;
  appId: string;
  apiUrl: string;
  apiKey: string;
  asr: string;
  voice: string;
  enableInterrupt: boolean;
};

type DifyForm = {
  name: string;
  apiUrl: string;
  apiKey: string;
  asr: string;
  voice: string;
  enableInterrupt: boolean;
};

type OpenCodeForm = {
  name: string;
  prompt: string;
  apiUrl: string;
  apiKey: string;
  model: string;
  asr: string;
  voice: string;
  enableInterrupt: boolean;
};

type AssistantType = "提示词" | "工作流" | "Dify" | "FastGPT" | "OpenCode";

const assistantTypes: AssistantType[] = [
  "提示词",
  "工作流",
  "Dify",
  "FastGPT",
  "OpenCode",
];

// 后端 type(英文) ↔ 列表展示标签(中文)
const typeToLabel: Record<ApiAssistantType, AssistantType> = {
  prompt: "提示词",
  workflow: "工作流",
  dify: "Dify",
  fastgpt: "FastGPT",
  opencode: "OpenCode",
};
const typeFromLabel: Record<AssistantType, ApiAssistantType> = {
  提示词: "prompt",
  工作流: "workflow",
  Dify: "dify",
  FastGPT: "fastgpt",
  OpenCode: "opencode",
};

// 后端 type → 编辑器视图(工作流暂为占位页)
const typeToView = {
  prompt: "create",
  dify: "create-dify",
  fastgpt: "create-fastgpt",
  opencode: "create-opencode",
  workflow: "placeholder",
} as const;

type View = "list" | "choose" | "loading" | (typeof typeToView)[ApiAssistantType];

// 路由驱动的页面模式:
// /assistants → list | /assistants/new → choose(引导,确认即建库) | /assistants/[id] → edit
export type AssistantPageProps =
  | { mode: "list" }
  | { mode: "choose" }
  | { mode: "edit"; assistantId: string };

// 各类型的空白表单模板(新建用)
function blankPromptForm(name: string): AssistantForm {
  return {
    name,
    greeting: "",
    prompt: "",
    runtimeMode: "pipeline",
    realtimeModel: "",
    model: "",
    asr: "",
    voice: "",
    knowledgeBase: "",
    enableInterrupt: true,
  };
}

function blankFastGptForm(name: string): FastGptForm {
  return {
    name,
    appId: "",
    apiUrl: "",
    apiKey: "",
    asr: "",
    voice: "",
    enableInterrupt: true,
  };
}

function blankDifyForm(name: string): DifyForm {
  return {
    name,
    apiUrl: "",
    apiKey: "",
    asr: "",
    voice: "",
    enableInterrupt: true,
  };
}

function blankOpenCodeForm(name: string): OpenCodeForm {
  return {
    name,
    prompt: "",
    apiUrl: "",
    apiKey: "",
    model: "",
    asr: "",
    voice: "",
    enableInterrupt: true,
  };
}
function formatTimestamp(iso?: string | null): string {
  if (!iso) return "";
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return "";
  const pad = (n: number) => String(n).padStart(2, "0");
  return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(
    d.getHours(),
  )}:${pad(d.getMinutes())}`;
}

type AssistantTypeOption = {
  type: AssistantType;
  label: string;
  description: string;
  icon: React.ReactNode;
  /** 提示词、Dify、FastGPT 类型已落地，工作流暂时显示占位页 */
  available: boolean;
};

const assistantTypeOptions: AssistantTypeOption[] = [
  {
    type: "提示词",
    label: "使用提示词构建",
    description: "通过提示词、模型与语音快速搭建对话助手，适合大多数场景。",
    icon: <MessageSquareText size={20} />,
    available: true,
  },
  {
    type: "工作流",
    label: "使用工作流构建",
    description: "用可视化编排串联多个节点，适合多步骤、带分支的复杂流程。",
    icon: <Workflow size={20} />,
    available: false,
  },
  {
    type: "Dify",
    label: "使用 Dify 构建",
    description: "对接 Dify 应用，复用其编排能力与知识库配置。",
    icon: <Boxes size={20} />,
    available: true,
  },
  {
    type: "FastGPT",
    label: "使用 FastGPT 构建",
    description: "对接 FastGPT 应用，复用其知识库问答与工作流能力。",
    icon: <Database size={20} />,
    available: true,
  },
  {
    type: "OpenCode",
    label: "使用 OpenCode 构建",
    description: "对接 OpenCode 服务，通过提示词驱动代码助手并支持实时语音对话。",
    icon: <Terminal size={20} />,
    available: true,
  },
];

type AssistantListItem = {
  id: string;
  name: string;
  type: AssistantType;
  updatedAt: string;
  /** 原始 ISO 时间戳,用于按时间排序(updatedAt 为展示用整形字符串) */
  updatedAtRaw: string | null | undefined;
};

type TypeFilter = "全部" | AssistantType;

const typeFilters: TypeFilter[] = ["全部", ...assistantTypes];

// 列表按更新时间排序:newest=最近更新在前(倒叙,默认) / oldest=最早更新在前
type SortOrder = "newest" | "oldest";

export function AssistantPage(props: AssistantPageProps) {
  const router = useRouter();
  // 编辑中的助手 id(来自路由)
  const editingId = props.mode === "edit" ? props.assistantId : null;

  const [form, setForm] = useState<AssistantForm>(() => blankPromptForm(""));
  const [fastGptForm, setFastGptForm] = useState<FastGptForm>(() =>
    blankFastGptForm(""),
  );
  const [difyForm, setDifyForm] = useState<DifyForm>(() => blankDifyForm(""));
  const [openCodeForm, setOpenCodeForm] = useState<OpenCodeForm>(() =>
    blankOpenCodeForm(""),
  );
  const [assistants, setAssistants] = useState<Assistant[]>([]);
  const [listLoading, setListLoading] = useState(true);
  const [listError, setListError] = useState<string | null>(null);
  const [saving, setSaving] = useState(false);
  const [saveError, setSaveError] = useState<string | null>(null);
  // 编辑模式:加载指定 id 助手失败时的错误
  const [loadError, setLoadError] = useState<string | null>(null);
  // 编辑模式:后端返回的打码 API Key(用于编辑页展示"当前密钥")
  const [storedApiKeyMask, setStoredApiKeyMask] = useState("");
  // 下拉数据源:模型资源 + 知识库
  const [modelResources, setModelResources] = useState<ModelResource[]>([]);
  const [knowledgeBases, setKnowledgeBases] = useState<KnowledgeBase[]>([]);
  // 视图由路由模式决定;仅编辑模式需要先 loading,等拿到助手类型后切换
  const [view, setView] = useState<View>(() => {
    if (props.mode === "choose") return "choose";
    if (props.mode === "edit") return "loading";
    return "list";
  });
  const [searchQuery, setSearchQuery] = useState("");
  const [typeFilter, setTypeFilter] = useState<TypeFilter>("全部");
  const [sortOrder, setSortOrder] = useState<SortOrder>("newest");
  const [currentPage, setCurrentPage] = useState(1);
  // choose 步骤的草稿：名称与已选类型，确认后直接建库并进入编辑页
  // (工作流占位页也用它展示名称与类型)
  const [draftName, setDraftName] = useState("");
  const [draftType, setDraftType] = useState<AssistantType | null>(null);
  // 引导页:创建请求进行中 / 创建失败
  const [creating, setCreating] = useState(false);
  const [createError, setCreateError] = useState<string | null>(null);
  // 已保存基线(当前类型表单的 JSON);与表单不一致时保存按钮才可点击
  const [savedSnapshot, setSavedSnapshot] = useState<string | null>(null);

  const loadAssistants = useCallback(async () => {
    setListLoading(true);
    setListError(null);
    try {
      setAssistants(await assistantsApi.list());
    } catch (error) {
      setListError(error instanceof Error ? error.message : "加载失败");
    } finally {
      setListLoading(false);
    }
  }, []);

  useEffect(() => {
    // 列表页挂载时拉取助手列表(与后端同步)
    if (props.mode !== "list") return;
    // eslint-disable-next-line react-hooks/set-state-in-effect
    void loadAssistants();
  }, [props.mode, loadAssistants]);

  // 进入创建/编辑前加载下拉数据源(模型资源 + 知识库)
  const loadResources = useCallback(async () => {
    try {
      const [creds, kbs] = await Promise.all([
        modelResourcesApi.list(),
        knowledgeBasesApi.list(),
      ]);
      setModelResources(creds);
      setKnowledgeBases(kbs);
    } catch {
      // 拉取失败时下拉为空,不阻塞表单
    }
  }, []);

  // 编辑页挂载时加载下拉数据源
  useEffect(() => {
    if (props.mode !== "edit") return;
    // eslint-disable-next-line react-hooks/set-state-in-effect
    void loadResources();
  }, [props.mode, loadResources]);

  // 按资源类型生成 {value:id, label:name} 选项
  const credOptions = (type: ModelResource["capability"]) =>
    modelResources
      .filter((c) => c.capability === type)
      .map((c) => ({ value: c.id, label: c.name }));
  const kbOptions = knowledgeBases.map((k) => ({ value: k.id, label: k.name }));

  function startCreate() {
    router.push("/assistants/new");
  }

  // 把后端 Assistant 回填进提示词表单(model/asr/voice 等存模型资源 id)
  // 返回回填后的表单,供调用方记录"已保存基线"
  function fillPromptForm(a: Assistant): AssistantForm {
    const next: AssistantForm = {
      name: a.name,
      greeting: a.greeting,
      prompt: a.prompt,
      runtimeMode: a.runtimeMode,
      realtimeModel: a.modelResourceIds.Realtime ?? "",
      model: a.modelResourceIds.LLM ?? "",
      asr: a.modelResourceIds.ASR ?? "",
      voice: a.modelResourceIds.TTS ?? "",
      knowledgeBase: a.knowledgeBaseId ?? "",
      enableInterrupt: a.enableInterrupt,
    };
    setForm(next);
    return next;
  }

  // 编辑：跳转到 /assistants/[id],由编辑页按助手类型回填表单
  function handleEdit(assistant: AssistantListItem) {
    router.push(`/assistants/${assistant.id}`);
  }

  // 引导页确认：直接创建到数据库拿到 id，再进入该助手的编辑页
  async function confirmType() {
    if (!draftName.trim() || !draftType || creating) {
      return;
    }

    setCreating(true);
    setCreateError(null);
    try {
      const created = await assistantsApi.create(
        baseUpsert({
          name: draftName.trim(),
          type: typeFromLabel[draftType],
        }),
      );
      router.push(`/assistants/${created.id}`);
    } catch (error) {
      setCreateError(error instanceof Error ? error.message : "创建失败");
      setCreating(false);
    }
  }

  // 复制助手：服务端整行复制(含真 key,密钥不经浏览器)
  async function handleDuplicate(assistant: AssistantListItem) {
    try {
      await assistantsApi.duplicate(assistant.id);
      await loadAssistants();
    } catch (error) {
      setListError(error instanceof Error ? error.message : "复制失败");
    }
  }

  // 删除助手
  async function handleDelete(id: string) {
    try {
      await assistantsApi.remove(id);
      await loadAssistants();
    } catch (error) {
      setListError(error instanceof Error ? error.message : "删除失败");
    }
  }

  // 平铺字段的空白 upsert,各类型按需覆盖(后端会按 type 清掉无关字段)
  function baseUpsert(over: Partial<AssistantUpsert>): AssistantUpsert {
    return {
      name: "",
      type: "prompt",
      runtimeMode: "pipeline",
      greeting: "",
      enableInterrupt: true,
      modelResourceIds: {},
      knowledgeBaseId: null,
      prompt: "",
      apiUrl: "",
      apiKey: "",
      appId: "",
      graph: {},
      ...over,
    };
  }

  // 保存:停留在编辑页,刷新密钥掩码并把当前表单记为已保存基线(按钮随之置灰)
  async function save(payload: AssistantUpsert) {
    if (!editingId) return;
    setSaving(true);
    setSaveError(null);
    try {
      const updated = await assistantsApi.update(editingId, payload);
      setStoredApiKeyMask(updated.apiKey ?? "");
      // 密钥输入框清空(空值=保留已存密钥),并以清空后的表单为新基线
      if (view === "create") {
        setSavedSnapshot(JSON.stringify(form));
      } else if (view === "create-dify") {
        const next = { ...difyForm, apiKey: "" };
        setDifyForm(next);
        setSavedSnapshot(JSON.stringify(next));
      } else if (view === "create-fastgpt") {
        const next = { ...fastGptForm, apiKey: "" };
        setFastGptForm(next);
        setSavedSnapshot(JSON.stringify(next));
      } else if (view === "create-opencode") {
        const next = { ...openCodeForm, apiKey: "" };
        setOpenCodeForm(next);
        setSavedSnapshot(JSON.stringify(next));
      }
    } catch (error) {
      setSaveError(error instanceof Error ? error.message : "保存失败");
    } finally {
      setSaving(false);
    }
  }

  function handleSavePrompt() {
    void save(
      baseUpsert({
        name: form.name.trim(),
        type: "prompt",
        runtimeMode: form.runtimeMode,
        greeting: form.greeting,
        enableInterrupt: form.enableInterrupt,
        modelResourceIds: {
          ...(form.model ? { LLM: form.model } : {}),
          ...(form.asr ? { ASR: form.asr } : {}),
          ...(form.voice ? { TTS: form.voice } : {}),
          ...(form.realtimeModel ? { Realtime: form.realtimeModel } : {}),
        },
        knowledgeBaseId: form.knowledgeBase || null,
        prompt: form.prompt,
      }),
    );
  }

  // ---- Dify ----
  function fillDifyForm(a: Assistant): DifyForm {
    const next: DifyForm = {
      name: a.name,
      apiUrl: a.apiUrl,
      // 编辑时不把打码占位符放入输入框；空值写回后端表示保留旧 key
      apiKey: "",
      asr: a.modelResourceIds.ASR ?? "",
      voice: a.modelResourceIds.TTS ?? "",
      enableInterrupt: a.enableInterrupt,
    };
    setDifyForm(next);
    return next;
  }

  function handleSaveDify() {
    void save(
      baseUpsert({
        name: difyForm.name.trim(),
        type: "dify",
        enableInterrupt: difyForm.enableInterrupt,
        modelResourceIds: {
          ...(difyForm.asr ? { ASR: difyForm.asr } : {}),
          ...(difyForm.voice ? { TTS: difyForm.voice } : {}),
        },
        apiUrl: difyForm.apiUrl,
        apiKey: difyForm.apiKey,
      }),
    );
  }

  // ---- FastGPT ----
  function fillFastGptForm(a: Assistant): FastGptForm {
    const next: FastGptForm = {
      name: a.name,
      appId: a.appId,
      apiUrl: a.apiUrl,
      // 编辑时不把打码占位符放入输入框；空值写回后端表示保留旧 key
      apiKey: "",
      asr: a.modelResourceIds.ASR ?? "",
      voice: a.modelResourceIds.TTS ?? "",
      enableInterrupt: a.enableInterrupt,
    };
    setFastGptForm(next);
    return next;
  }

  function handleSaveFastGpt() {
    void save(
      baseUpsert({
        name: fastGptForm.name.trim(),
        type: "fastgpt",
        enableInterrupt: fastGptForm.enableInterrupt,
        modelResourceIds: {
          ...(fastGptForm.asr ? { ASR: fastGptForm.asr } : {}),
          ...(fastGptForm.voice ? { TTS: fastGptForm.voice } : {}),
        },
        appId: fastGptForm.appId,
        apiUrl: fastGptForm.apiUrl,
        apiKey: fastGptForm.apiKey,
      }),
    );
  }

  // ---- OpenCode ----
  function fillOpenCodeForm(a: Assistant): OpenCodeForm {
    const next: OpenCodeForm = {
      name: a.name,
      prompt: a.prompt,
      apiUrl: a.apiUrl,
      // 编辑时不把打码占位符放入输入框；空值写回后端表示保留旧 key
      apiKey: "",
      model: a.modelResourceIds.LLM ?? "",
      asr: a.modelResourceIds.ASR ?? "",
      voice: a.modelResourceIds.TTS ?? "",
      enableInterrupt: a.enableInterrupt,
    };
    setOpenCodeForm(next);
    return next;
  }

  // 编辑模式:按路由中的 id 拉取助手,回填对应类型的表单后再切换视图
  useEffect(() => {
    if (props.mode !== "edit" || !editingId) return;
    let cancelled = false;

    void (async () => {
      try {
        const assistant = await assistantsApi.get(editingId);
        if (cancelled) return;
        setStoredApiKeyMask(assistant.apiKey ?? "");
        if (assistant.type === "prompt") {
          setSavedSnapshot(JSON.stringify(fillPromptForm(assistant)));
        } else if (assistant.type === "fastgpt") {
          setSavedSnapshot(JSON.stringify(fillFastGptForm(assistant)));
        } else if (assistant.type === "dify") {
          setSavedSnapshot(JSON.stringify(fillDifyForm(assistant)));
        } else if (assistant.type === "opencode") {
          setSavedSnapshot(JSON.stringify(fillOpenCodeForm(assistant)));
        } else {
          // 工作流：暂时显示占位页
          setDraftName(assistant.name);
          setDraftType(typeToLabel[assistant.type]);
        }
        setView(typeToView[assistant.type]);
      } catch (error) {
        if (!cancelled) {
          setLoadError(
            error instanceof Error ? error.message : "加载助手失败",
          );
        }
      }
    })();

    return () => {
      cancelled = true;
    };
  }, [props.mode, editingId]);

  function handleSaveOpenCode() {
    void save(
      baseUpsert({
        name: openCodeForm.name.trim(),
        type: "opencode",
        enableInterrupt: openCodeForm.enableInterrupt,
        modelResourceIds: {
          ...(openCodeForm.model ? { LLM: openCodeForm.model } : {}),
          ...(openCodeForm.asr ? { ASR: openCodeForm.asr } : {}),
          ...(openCodeForm.voice ? { TTS: openCodeForm.voice } : {}),
        },
        prompt: openCodeForm.prompt,
        apiUrl: openCodeForm.apiUrl,
        apiKey: openCodeForm.apiKey,
      }),
    );
  }

  // 当前编辑器表单相对已保存基线是否有改动(决定保存按钮是否可点)
  const activeFormJson =
    view === "create"
      ? JSON.stringify(form)
      : view === "create-dify"
        ? JSON.stringify(difyForm)
        : view === "create-fastgpt"
          ? JSON.stringify(fastGptForm)
          : view === "create-opencode"
            ? JSON.stringify(openCodeForm)
            : null;
  const dirty =
    activeFormJson !== null &&
    savedSnapshot !== null &&
    activeFormJson !== savedSnapshot;

  const listItems: AssistantListItem[] = assistants.map((a) => ({
    id: a.id,
    name: a.name,
    type: typeToLabel[a.type],
    updatedAt: formatTimestamp(a.updatedAt),
    updatedAtRaw: a.updatedAt,
  }));
  const filteredAssistants = listItems.filter((assistant) => {
    if (typeFilter !== "全部" && assistant.type !== typeFilter) {
      return false;
    }

    const keyword = searchQuery.trim().toLowerCase();

    if (!keyword) {
      return true;
    }

    return [assistant.name, assistant.id, assistant.type, assistant.updatedAt]
      .join(" ")
      .toLowerCase()
      .includes(keyword);
  });

  // 按更新时间排序(以原始 ISO 时间戳为准);缺失时间戳视为最早,id 作为稳定的次级排序键
  const timeValue = (iso: string | null | undefined) => {
    const t = iso ? new Date(iso).getTime() : NaN;
    return Number.isNaN(t) ? 0 : t;
  };
  const sortedAssistants = [...filteredAssistants].sort((a, b) => {
    const diff = timeValue(b.updatedAtRaw) - timeValue(a.updatedAtRaw);
    if (diff !== 0) return sortOrder === "newest" ? diff : -diff;
    return a.id.localeCompare(b.id);
  });

  const pageSize = 5;
  const totalPages = Math.max(1, Math.ceil(sortedAssistants.length / pageSize));
  const safeCurrentPage = Math.min(currentPage, totalPages);
  const pageStart = (safeCurrentPage - 1) * pageSize;
  const pageEnd = pageStart + pageSize;
  const paginatedAssistants = sortedAssistants.slice(pageStart, pageEnd);

  function handleSearchChange(value: string) {
    setSearchQuery(value);
    setCurrentPage(1);
  }

  function handleFilterChange(filter: TypeFilter) {
    setTypeFilter(filter);
    setCurrentPage(1);
  }

  function handleSortChange(order: SortOrder) {
    setSortOrder(order);
    setCurrentPage(1);
  }

  function toggleSortOrder() {
    handleSortChange(sortOrder === "newest" ? "oldest" : "newest");
  }

  function updateForm<K extends keyof AssistantForm>(
    key: K,
    value: AssistantForm[K],
  ) {
    setForm((prev) => ({
      ...prev,
      [key]: value,
    }));
  }

  function updateFastGptForm<K extends keyof FastGptForm>(
    key: K,
    value: FastGptForm[K],
  ) {
    setFastGptForm((prev) => ({
      ...prev,
      [key]: value,
    }));
  }

  function updateDifyForm<K extends keyof DifyForm>(
    key: K,
    value: DifyForm[K],
  ) {
    setDifyForm((prev) => ({
      ...prev,
      [key]: value,
    }));
  }

  function updateOpenCodeForm<K extends keyof OpenCodeForm>(
    key: K,
    value: OpenCodeForm[K],
  ) {
    setOpenCodeForm((prev) => ({
      ...prev,
      [key]: value,
    }));
  }
  if (view === "loading") {
    return (
      <div className="flex h-full items-center justify-center">
        {loadError ? (
          <div className="text-center">
            <div className="font-medium text-destructive">加载助手失败</div>
            <div className="mt-2 text-sm text-muted-foreground">
              {loadError}
            </div>
            <Button
              variant="outline"
              size="sm"
              className="mt-4 border-hairline-strong text-muted-foreground hover:text-foreground"
              onClick={() => router.push("/assistants")}
            >
              返回列表
            </Button>
          </div>
        ) : (
          <div className="flex items-center gap-2 text-sm text-muted-foreground">
            <Loader2 size={16} className="animate-spin" />
            正在加载助手…
          </div>
        )}
      </div>
    );
  }

  if (view === "list") {
    return (
      <div className="mx-auto flex w-full max-w-[1440px] flex-col gap-8">
        <PageHeader
          title="助手列表"
          description="管理已有的视频助手，支持提示词、工作流、Dify 和 FastGPT 类型。"
          action={
            <Button
              size="lg"
              className="w-full gap-2 sm:w-auto"
              onClick={startCreate}
            >
              <Plus size={16} />
              创建助手
            </Button>
          }
        />

        <section className="rounded-2xl border border-hairline bg-card p-6 shadow-sm">
          <ListToolbar
            filters={
              <FilterPills
                options={typeFilters}
                value={typeFilter}
                onChange={handleFilterChange}
              />
            }
            search={
              <SearchInput
                value={searchQuery}
                onChange={handleSearchChange}
                placeholder="搜索助手名称、类型或 ID..."
                className="lg:w-[320px]"
              />
            }
          />

          <DataList<AssistantListItem>
            rows={paginatedAssistants}
            rowKey={(assistant) => assistant.id}
            loading={listLoading}
            loadingText="正在加载助手列表…"
            error={listError}
            onRetry={() => void loadAssistants()}
            empty={{
              title: listItems.length === 0 ? "暂无助手" : "未找到匹配的助手",
              description:
                listItems.length === 0
                  ? "点击右上角「创建助手」开始。"
                  : "请调整关键词或筛选条件后再试。",
            }}
            pagination={{
              page: safeCurrentPage,
              totalPages,
              onPageChange: setCurrentPage,
              summary:
                filteredAssistants.length === 0
                  ? "没有数据"
                  : `显示 ${pageStart + 1}-${Math.min(pageEnd, filteredAssistants.length)} / 共 ${filteredAssistants.length} 个助手`,
            }}
            columns={[
              {
                key: "name",
                header: "助手名称",
                width: "md:w-[360px]",
                cell: (assistant) => (
                  <>
                    <div className="truncate font-medium text-foreground">
                      {assistant.name}
                    </div>
                    <div className="mt-1 text-xs text-muted-soft">
                      {assistant.id}
                    </div>
                  </>
                ),
              },
              {
                key: "type",
                header: "助手类型",
                width: "md:w-[128px]",
                cell: (assistant) => (
                  <Badge
                    variant="secondary"
                    className="h-6 bg-surface-strong px-3 text-muted-foreground"
                  >
                    {assistant.type}
                  </Badge>
                ),
              },
              {
                key: "updatedAt",
                width: "md:w-[176px]",
                header: (
                  <button
                    type="button"
                    onClick={toggleSortOrder}
                    className="caption-label -mx-2 inline-flex items-center gap-1 rounded-md px-2 py-1 text-muted-soft transition-colors hover:bg-surface-strong hover:text-foreground"
                    aria-label={
                      sortOrder === "newest"
                        ? "当前按最近更新排序,点击切换为最早更新"
                        : "当前按最早更新排序,点击切换为最近更新"
                    }
                  >
                    更新时间
                    {sortOrder === "newest" ? (
                      <ChevronDown size={13} />
                    ) : (
                      <ChevronUp size={13} />
                    )}
                  </button>
                ),
                cellClassName:
                  "whitespace-nowrap tabular-nums text-muted-foreground",
                cell: (assistant) => assistant.updatedAt,
              },
              {
                key: "actions",
                header: "操作",
                align: "right",
                cellClassName: "flex justify-end gap-2",
                cell: (assistant) => (
                  <>
                    <Button
                      variant="outline"
                      size="sm"
                      className="gap-1.5 border-hairline-strong text-xs text-muted-foreground hover:text-foreground"
                      onClick={() => handleEdit(assistant)}
                    >
                      <Pencil size={14} />
                      编辑
                    </Button>

                    <DropdownMenu>
                      <DropdownMenuTrigger asChild>
                        <Button
                          variant="outline"
                          size="icon-sm"
                          className="border-hairline-strong text-muted-foreground hover:text-foreground"
                          aria-label={`${assistant.name} 更多操作`}
                        >
                          <MoreHorizontal size={15} />
                        </Button>
                      </DropdownMenuTrigger>
                      <DropdownMenuContent
                        align="end"
                        className="w-32 min-w-32 rounded-xl border border-hairline bg-popover p-1"
                      >
                        <DropdownMenuItem
                          className="rounded-lg"
                          onSelect={() => handleDuplicate(assistant)}
                        >
                          <Copy size={14} />
                          复制
                        </DropdownMenuItem>
                        <DropdownMenuItem
                          variant="destructive"
                          className="rounded-lg"
                          onSelect={(event) => {
                            event.preventDefault();
                            void handleDelete(assistant.id);
                          }}
                        >
                          <Trash2 size={14} />
                          删除
                        </DropdownMenuItem>
                      </DropdownMenuContent>
                    </DropdownMenu>
                  </>
                ),
              },
            ]}
          />
        </section>
      </div>
    );
  }

  if (view === "choose") {
    return (
      <div className="mx-auto flex w-full max-w-[1180px] flex-col gap-8">
        <div className="flex items-start justify-between gap-6">
          <div>
            <h1 className="font-display display-lg text-ink">创建助手</h1>
            <p className="mt-3 max-w-2xl text-[15px] leading-7 text-muted-foreground">
              先为助手取个名字，再选择构建方式。确认后将立即创建助手并进入编辑页。
            </p>
          </div>

          <Button
            variant="outline"
            size="lg"
            className="shrink-0 gap-2 border-hairline-strong text-muted-foreground hover:text-foreground"
            onClick={() => router.push("/assistants")}
          >
            <ChevronLeft size={16} />
            返回列表
          </Button>
        </div>

        <section className="rounded-2xl border border-hairline bg-card p-6 shadow-sm">
          <label className="block">
            <div className="mb-2 text-sm font-medium text-foreground">
              助手名称
            </div>
            <Input
              value={draftName}
              autoFocus
              onChange={(event) => setDraftName(event.target.value)}
              placeholder="请输入助手名称"
              className="border-hairline-strong bg-background text-foreground placeholder:text-muted-soft"
            />
          </label>
        </section>

        <section className="flex flex-col gap-4">
          <div className="text-sm font-medium text-foreground">构建方式</div>

          <div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
            {assistantTypeOptions.map((option) => {
              const selected = draftType === option.type;

              return (
                <button
                  key={option.type}
                  type="button"
                  onClick={() => setDraftType(option.type)}
                  className={`group relative flex flex-col gap-4 rounded-2xl border bg-card p-5 text-left transition-colors ${
                    selected
                      ? "border-primary ring-1 ring-primary"
                      : "border-hairline hover:border-hairline-strong"
                  }`}
                >
                  <div className="flex items-center justify-between">
                    <div className="flex h-11 w-11 items-center justify-center rounded-full bg-surface-strong text-foreground">
                      {option.icon}
                    </div>

                    {selected ? (
                      <span className="flex h-6 w-6 items-center justify-center rounded-full bg-primary text-primary-foreground">
                        <Check size={14} />
                      </span>
                    ) : (
                      !option.available && (
                        <Badge
                          variant="secondary"
                          className="h-6 bg-surface-strong px-3 text-xs text-muted-foreground"
                        >
                          即将上线
                        </Badge>
                      )
                    )}
                  </div>

                  <div>
                    <div className="text-base font-medium text-foreground">
                      {option.label}
                    </div>
                    <p className="mt-1.5 text-sm leading-6 text-muted-foreground">
                      {option.description}
                    </p>
                  </div>
                </button>
              );
            })}
          </div>
        </section>

        <div className="flex items-center justify-end gap-3">
          {createError && (
            <span className="text-xs text-destructive">{createError}</span>
          )}
          <Button
            variant="outline"
            size="lg"
            className="border-hairline-strong text-muted-foreground hover:text-foreground"
            disabled={creating}
            onClick={() => router.push("/assistants")}
          >
            取消
          </Button>
          <Button
            size="lg"
            className="gap-2"
            disabled={!draftName.trim() || !draftType || creating}
            onClick={() => void confirmType()}
          >
            {creating ? (
              <Loader2 size={16} className="animate-spin" />
            ) : (
              <Rocket size={16} />
            )}
            创建助手
          </Button>
        </div>
      </div>
    );
  }

  if (view === "placeholder") {
    return (
      <div className="mx-auto flex w-full max-w-[1180px] flex-col gap-6">
        <div className="flex items-start justify-between gap-6">
          <div>
            <h1 className="font-display display-lg text-ink">
              {draftName.trim() || "创建助手"}
            </h1>
            <p className="mt-3 max-w-2xl text-[15px] leading-7 text-muted-foreground">
              {draftType} 构建方式正在开发中，敬请期待。
            </p>
          </div>

          <Button
            variant="outline"
            size="lg"
            className="shrink-0 gap-2 border-hairline-strong text-muted-foreground hover:text-foreground"
            onClick={() => router.push("/assistants")}
          >
            <ChevronLeft size={16} />
            返回列表
          </Button>
        </div>

        <section className="relative overflow-hidden rounded-3xl border border-hairline bg-canvas-soft px-10 py-20 text-center">
          <div
            aria-hidden
            className="pointer-events-none absolute -right-24 top-0 h-72 w-72 rounded-full opacity-50 blur-3xl"
            style={{
              backgroundImage:
                "radial-gradient(circle, color-mix(in srgb, var(--gradient-sky) 50%, transparent), transparent 70%)",
            }}
          />
          <div
            aria-hidden
            className="pointer-events-none absolute -left-20 bottom-0 h-64 w-64 rounded-full opacity-45 blur-3xl"
            style={{
              backgroundImage:
                "radial-gradient(circle, color-mix(in srgb, var(--gradient-lavender) 50%, transparent), transparent 70%)",
            }}
          />
          <div className="relative">
            <div className="caption-label inline-flex rounded-full bg-surface-strong px-3 py-1 text-muted-foreground">
              建设中
            </div>
            <p className="font-display display-sm mx-auto mt-5 max-w-md text-ink">
              {draftType} 构建页面正在编写中
            </p>
            <p className="mx-auto mt-3 max-w-md text-sm leading-7 text-body">
              页面骨架与设计语言已就绪，后续将填充具体构建流程与数据。
            </p>
          </div>
        </section>
      </div>
    );
  }

  if (view === "create-dify") {
    return (
      <div className="-mt-6 flex h-full flex-col gap-4">
        <div className="flex shrink-0 items-center justify-between gap-6 border-b border-hairline pb-3 pt-1">
          <div className="flex min-w-0 items-center gap-2">
            <EditorBackButton onClick={() => router.push("/assistants")} />
            <EditableTitle
              value={difyForm.name}
              onChange={(value) => updateDifyForm("name", value)}
            />
            <AssistantIdentity assistantId={editingId} />
          </div>

          <div className="flex shrink-0 gap-2">
            {saveError && (
              <span className="self-center text-xs text-destructive">
                {saveError}
              </span>
            )}
            <Button
              className="gap-2"
              disabled={saving || !dirty || !difyForm.name.trim()}
              onClick={() => void handleSaveDify()}
            >
              {saving ? (
                <Loader2 size={16} className="animate-spin" />
              ) : (
                <Save size={16} />
              )}
              保存
            </Button>
          </div>
        </div>

        <div className="flex min-h-0 flex-1 gap-6">
          <div className="min-w-0 flex-1 space-y-5 overflow-y-auto pr-1">
            <SectionCard
              icon={<Boxes size={18} />}
              title="Dify 应用配置"
              description="填写 API URL 与 API Key 以对接 Dify 应用。开场白、知识库、提示词等对话编排请在 Dify 平台配置，本页不重复设置。"
            >
              <InputField
                label="API URL"
                value={difyForm.apiUrl}
                onChange={(value) => updateDifyForm("apiUrl", value)}
                placeholder="https://api.dify.ai/v1/chat-messages"
              />
              <SecretInputField
                label="API Key"
                value={difyForm.apiKey}
                onChange={(value) => updateDifyForm("apiKey", value)}
                placeholder="请输入 Dify API Key"
                storedValueMask={storedApiKeyMask}
              />
            </SectionCard>

            <SectionCard
              icon={<Brain size={18} />}
              title="语音配置"
              description="从「模型资源」中选择语音识别与语音合成。大模型、知识库与开场白由 Dify 应用提供，请前往 Dify 平台配置。"
            >
              <ResourceSelectField
                label="语音识别"
                value={difyForm.asr}
                onChange={(value) => updateDifyForm("asr", value)}
                options={credOptions("ASR")}
                noneLabel="无"
              />
              <ResourceSelectField
                label="语音合成"
                value={difyForm.voice}
                onChange={(value) => updateDifyForm("voice", value)}
                options={credOptions("TTS")}
                noneLabel="无"
              />
            </SectionCard>

            <SectionCard
              icon={<Sparkles size={18} />}
              title="交互策略"
              description="设置实时视频对话时的交互体验"
            >
              <ToggleRow
                title="允许用户打断"
                description="用户说话时，助手可以停止当前播报并重新理解用户输入"
                checked={difyForm.enableInterrupt}
                onChange={(checked) =>
                  updateDifyForm("enableInterrupt", checked)
                }
              />
            </SectionCard>
          </div>

          <DebugDrawer assistantId={editingId} />
        </div>
      </div>
    );
  }

  if (view === "create-fastgpt") {
    return (
      <div className="-mt-6 flex h-full flex-col gap-4">
        <div className="flex shrink-0 items-center justify-between gap-6 border-b border-hairline pb-3 pt-1">
          <div className="flex min-w-0 items-center gap-2">
            <EditorBackButton onClick={() => router.push("/assistants")} />
            <EditableTitle
              value={fastGptForm.name}
              onChange={(value) => updateFastGptForm("name", value)}
            />
            <AssistantIdentity assistantId={editingId} />
          </div>

          <div className="flex shrink-0 gap-2">
            {saveError && (
              <span className="self-center text-xs text-destructive">
                {saveError}
              </span>
            )}
            <Button
              className="gap-2"
              disabled={saving || !dirty || !fastGptForm.name.trim()}
              onClick={() => void handleSaveFastGpt()}
            >
              {saving ? (
                <Loader2 size={16} className="animate-spin" />
              ) : (
                <Save size={16} />
              )}
              保存
            </Button>
          </div>
        </div>

        <div className="flex min-h-0 flex-1 gap-6">
          <div className="min-w-0 flex-1 space-y-5 overflow-y-auto pr-1">
            <SectionCard
              icon={<Database size={18} />}
              title="FastGPT 应用配置"
              description="填写 App ID、API URL 与 API Key 以对接 FastGPT 应用。开场白、知识库、提示词等对话编排请在 FastGPT 平台配置，本页不重复设置。"
            >
              <InputField
                label="App ID"
                value={fastGptForm.appId}
                onChange={(value) => updateFastGptForm("appId", value)}
                placeholder="请输入 FastGPT 应用 ID"
              />
              <InputField
                label="API URL"
                value={fastGptForm.apiUrl}
                onChange={(value) => updateFastGptForm("apiUrl", value)}
                placeholder="https://api.fastgpt.in/api/v1/chat/completions"
              />
              <SecretInputField
                label="API Key"
                value={fastGptForm.apiKey}
                onChange={(value) => updateFastGptForm("apiKey", value)}
                placeholder="请输入 FastGPT API Key"
                storedValueMask={storedApiKeyMask}
              />
            </SectionCard>

            <SectionCard
              icon={<Brain size={18} />}
              title="语音配置"
              description="从「模型资源」中选择语音识别与语音合成。大模型、知识库与开场白由 FastGPT 应用提供，请前往 FastGPT 平台配置。"
            >
              <ResourceSelectField
                label="语音识别"
                value={fastGptForm.asr}
                onChange={(value) => updateFastGptForm("asr", value)}
                options={credOptions("ASR")}
                noneLabel="无"
              />
              <ResourceSelectField
                label="语音合成"
                value={fastGptForm.voice}
                onChange={(value) => updateFastGptForm("voice", value)}
                options={credOptions("TTS")}
                noneLabel="无"
              />
            </SectionCard>

            <SectionCard
              icon={<Sparkles size={18} />}
              title="交互策略"
              description="设置实时视频对话时的交互体验"
            >
              <ToggleRow
                title="允许用户打断"
                description="用户说话时，助手可以停止当前播报并重新理解用户输入"
                checked={fastGptForm.enableInterrupt}
                onChange={(checked) =>
                  updateFastGptForm("enableInterrupt", checked)
                }
              />
            </SectionCard>
          </div>

          <DebugDrawer assistantId={editingId} />
        </div>
      </div>
    );
  }

  if (view === "create-opencode") {
    return (
      <div className="-mt-6 flex h-full flex-col gap-4">
        <div className="flex shrink-0 items-center justify-between gap-6 border-b border-hairline pb-3 pt-1">
          <div className="flex min-w-0 items-center gap-2">
            <EditorBackButton onClick={() => router.push("/assistants")} />
            <EditableTitle
              value={openCodeForm.name}
              onChange={(value) => updateOpenCodeForm("name", value)}
            />
            <AssistantIdentity assistantId={editingId} />
          </div>

          <div className="flex shrink-0 gap-2">
            {saveError && (
              <span className="self-center text-xs text-destructive">
                {saveError}
              </span>
            )}
            <Button
              className="gap-2"
              disabled={saving || !dirty || !openCodeForm.name.trim()}
              onClick={() => void handleSaveOpenCode()}
            >
              {saving ? (
                <Loader2 size={16} className="animate-spin" />
              ) : (
                <Save size={16} />
              )}
              保存
            </Button>
          </div>
        </div>

        <div className="flex min-h-0 flex-1 gap-6">
          <div className="min-w-0 flex-1 space-y-5 overflow-y-auto pr-1">
            <SectionCard
              icon={<Terminal size={18} />}
              title="OpenCode 服务配置"
              description="填写 OpenCode 服务地址与 API Key 以对接代码助手。"
            >
              <InputField
                label="OpenCode URL"
                value={openCodeForm.apiUrl}
                onChange={(value) => updateOpenCodeForm("apiUrl", value)}
                placeholder="http://localhost:4096"
              />
              <SecretInputField
                label="API Key"
                value={openCodeForm.apiKey}
                onChange={(value) => updateOpenCodeForm("apiKey", value)}
                placeholder="请输入 OpenCode API Key"
                storedValueMask={storedApiKeyMask}
              />
            </SectionCard>

            <SectionCard
              icon={<MessageSquareText size={18} />}
              title="提示词"
              description="描述助手的角色、能力和回答要求"
            >
              <TextAreaField
                value={openCodeForm.prompt}
                onChange={(value) => updateOpenCodeForm("prompt", value)}
                placeholder="请输入提示词，描述助手的角色、能力和回答要求"
                rows={8}
              />
            </SectionCard>

            <SectionCard
              icon={<Brain size={18} />}
              title="模型与语音配置"
              description="配置 OpenCode 使用的大语言模型、语音识别与语音合成资源。"
            >
              <ResourceSelectField
                label="大语言模型"
                value={openCodeForm.model}
                onChange={(value) => updateOpenCodeForm("model", value)}
                options={credOptions("LLM")}
                noneLabel="无"
              />
              <ResourceSelectField
                label="语音识别"
                value={openCodeForm.asr}
                onChange={(value) => updateOpenCodeForm("asr", value)}
                options={credOptions("ASR")}
                noneLabel="无"
              />
              <ResourceSelectField
                label="语音合成"
                value={openCodeForm.voice}
                onChange={(value) => updateOpenCodeForm("voice", value)}
                options={credOptions("TTS")}
                noneLabel="无"
              />
            </SectionCard>

            <SectionCard
              icon={<Sparkles size={18} />}
              title="交互策略"
              description="设置实时视频对话时的交互体验"
            >
              <ToggleRow
                title="允许用户打断"
                description="用户说话时，助手可以停止当前播报并重新理解用户输入"
                checked={openCodeForm.enableInterrupt}
                onChange={(checked) =>
                  updateOpenCodeForm("enableInterrupt", checked)
                }
              />
            </SectionCard>
          </div>

          <DebugDrawer assistantId={editingId} />
        </div>
      </div>
    );
  }

  return (
    <div className="-mt-6 flex h-full flex-col gap-4">
      <div className="flex shrink-0 items-center justify-between gap-6 border-b border-hairline pb-3 pt-1">
        <div className="flex min-w-0 items-center gap-2">
          <EditorBackButton onClick={() => router.push("/assistants")} />
          <EditableTitle
            value={form.name}
            onChange={(value) => updateForm("name", value)}
          />
          <AssistantIdentity assistantId={editingId} />
        </div>

        <div className="flex shrink-0 gap-2">
          {saveError && (
            <span className="self-center text-xs text-destructive">
              {saveError}
            </span>
          )}
          <Button
            className="gap-2"
            disabled={saving || !dirty || !form.name.trim()}
            onClick={() => void handleSavePrompt()}
          >
            {saving ? (
              <Loader2 size={16} className="animate-spin" />
            ) : (
              <Save size={16} />
            )}
            保存
          </Button>
        </div>
      </div>

      <div className="flex min-h-0 flex-1 gap-6">
        <div className="min-w-0 flex-1 space-y-5 overflow-y-auto pr-1">
          <SectionCard>
            <div className="grid grid-cols-1 gap-4 md:grid-cols-2">
              <div
                role="button"
                tabIndex={0}
                onClick={() => updateForm("runtimeMode", "pipeline")}
                onKeyDown={(event) => {
                  if (event.key === "Enter" || event.key === " ") {
                    event.preventDefault();
                    updateForm("runtimeMode", "pipeline");
                  }
                }}
                className={[
                  "cursor-pointer rounded-2xl border p-5 text-left transition-colors",
                  form.runtimeMode === "pipeline"
                    ? "border-primary bg-primary/5 ring-1 ring-primary"
                    : "border-hairline bg-canvas-soft hover:border-hairline-strong",
                ].join(" ")}
              >
                <div className="flex items-center justify-between gap-3">
                  <div className="flex items-center gap-3">
                    <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-surface-strong text-foreground">
                      <Waypoints size={18} />
                    </div>
                    <div className="flex items-center gap-1.5">
                      <span className="font-medium text-foreground">Pipeline 模式</span>
                      <HelpHint text="通过 ASR、LLM 和 TTS 级联组成语音管线，灵活选配各模块。" />
                    </div>
                  </div>
                  {form.runtimeMode === "pipeline" && (
                    <span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground">
                      <Check size={14} />
                    </span>
                  )}
                </div>
              </div>

              <div
                role="button"
                tabIndex={0}
                onClick={() => updateForm("runtimeMode", "realtime")}
                onKeyDown={(event) => {
                  if (event.key === "Enter" || event.key === " ") {
                    event.preventDefault();
                    updateForm("runtimeMode", "realtime");
                  }
                }}
                className={[
                  "cursor-pointer rounded-2xl border p-5 text-left transition-colors",
                  form.runtimeMode === "realtime"
                    ? "border-primary bg-primary/5 ring-1 ring-primary"
                    : "border-hairline bg-canvas-soft hover:border-hairline-strong",
                ].join(" ")}
              >
                <div className="flex items-center justify-between gap-3">
                  <div className="flex items-center gap-3">
                    <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-surface-strong text-foreground">
                      <AudioLines size={18} />
                    </div>
                    <div className="flex items-center gap-1.5">
                      <span className="font-medium text-foreground">Realtime 模式</span>
                      <HelpHint text="使用原生实时语音模型，模型直接处理音频输入并生成语音回复。" />
                    </div>
                  </div>
                  {form.runtimeMode === "realtime" && (
                    <span className="flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground">
                      <Check size={14} />
                    </span>
                  )}
                </div>
              </div>
            </div>
          </SectionCard>

          <SectionCard
            icon={<MessageSquareText size={18} />}
            title="提示词"
            description="描述助手的角色、能力和回答要求"
          >
            <TextAreaField
              value={form.prompt}
              onChange={(value) => updateForm("prompt", value)}
              placeholder="请输入提示词，描述助手的角色、能力和回答要求"
              rows={8}
            />
          </SectionCard>

          {form.runtimeMode === "pipeline" ? (
            <SectionCard
              icon={<Brain size={18} />}
              title="模型配置"
              description="从「模型资源」中选择大语言模型、语音识别与语音合成"
            >
              <ResourceSelectField
                label="大语言模型"
                value={form.model}
                onChange={(value) => updateForm("model", value)}
                options={credOptions("LLM")}
                noneLabel="无"
              />
              <ResourceSelectField
                label="语音识别"
                value={form.asr}
                onChange={(value) => updateForm("asr", value)}
                options={credOptions("ASR")}
                noneLabel="无"
              />
              <ResourceSelectField
                label="语音合成"
                value={form.voice}
                onChange={(value) => updateForm("voice", value)}
                options={credOptions("TTS")}
                noneLabel="无"
              />
            </SectionCard>
          ) : (
            <SectionCard
              icon={<Brain size={18} />}
              title="模型配置"
              description="当前模式下 ASR 与 TTS 由 Realtime 模型内置完成"
            >
              <ResourceSelectField
                label="Realtime 模型"
                value={form.realtimeModel}
                onChange={(value) => updateForm("realtimeModel", value)}
                options={credOptions("Realtime")}
                noneLabel="无"
              />
            </SectionCard>
          )}

          <SectionCard
            icon={<Bot size={18} />}
            title="开场白"
            description="助手与用户首次对话时的开场语"
          >
            <TextAreaField
              value={form.greeting}
              onChange={(value) => updateForm("greeting", value)}
              placeholder="请输入助手开场白"
            />
          </SectionCard>

          <SectionCard
            icon={<Database size={18} />}
            title="知识库配置"
            description="选择助手回答时可检索的业务知识来源"
          >
            <ResourceSelectField
              value={form.knowledgeBase}
              onChange={(value) => updateForm("knowledgeBase", value)}
              options={kbOptions}
              noneLabel="无"
            />
          </SectionCard>

          <SectionCard
            icon={<Sparkles size={18} />}
            title="交互策略"
            description="设置实时视频对话时的交互体验"
          >
            <ToggleRow
              title="允许用户打断"
              description="用户说话时，助手可以停止当前播报并重新理解用户输入"
              checked={form.enableInterrupt}
              onChange={(checked) => updateForm("enableInterrupt", checked)}
            />
          </SectionCard>
        </div>

        <DebugDrawer assistantId={editingId} />
      </div>
    </div>
  );
}

type VizStyle = "aura" | "nebula" | "bars" | "wave";

const VIZ_OPTIONS: { style: VizStyle; label: string; icon: React.ReactNode }[] =
  [
    { style: "aura", label: "光环", icon: <Orbit size={14} /> },
    { style: "nebula", label: "星云", icon: <Sparkles size={14} /> },
    { style: "bars", label: "频谱", icon: <AudioLines size={14} /> },
    { style: "wave", label: "波形", icon: <Waves size={14} /> },
  ];

// 中央语音可视化(光环/星云/频谱/波形)暂时隐藏:调试面板固定为
// 「上聊天记录 + 下波形监控」布局。置 true 可恢复可视化视图与样式切换。
const SHOW_VOICE_VIZ = false;

function SegmentedIconGroup({
  children,
  label,
}: {
  children: React.ReactNode;
  label: string;
}) {
  return (
    <div
      role="group"
      aria-label={label}
      className="flex items-center gap-0.5 rounded-full border border-hairline bg-canvas-soft p-0.5"
    >
      {children}
    </div>
  );
}

function SegmentedIconButton({
  selected,
  label,
  onClick,
  children,
}: {
  selected: boolean;
  label: string;
  onClick: () => void;
  children: React.ReactNode;
}) {
  return (
    <button
      type="button"
      onClick={onClick}
      aria-label={label}
      aria-pressed={selected}
      title={label}
      className={[
        "flex h-7 w-7 items-center justify-center rounded-full transition-colors",
        selected
          ? "bg-surface-strong text-foreground shadow-sm"
          : "text-muted-soft hover:text-foreground",
      ].join(" ")}
    >
      {children}
    </button>
  );
}

function DebugDrawer({ assistantId }: { assistantId: string | null }) {
  const [showTranscript, setShowTranscript] = useState(false);
  const [vizStyle, setVizStyle] = useState<VizStyle>("aura");

  return (
    <aside className="hidden min-w-0 flex-1 flex-col overflow-hidden rounded-2xl border border-hairline bg-card shadow-sm lg:flex">
      <div className="flex shrink-0 items-center justify-between gap-3 border-b border-hairline px-5 py-3">
        <div className="text-sm font-medium text-foreground">调试与预览</div>
        {SHOW_VOICE_VIZ && (
          <div className="flex items-center gap-2">
            {!showTranscript && (
              <SegmentedIconGroup label="可视化样式">
                {VIZ_OPTIONS.map((option) => (
                  <SegmentedIconButton
                    key={option.style}
                    selected={vizStyle === option.style}
                    label={`可视化样式：${option.label}`}
                    onClick={() => setVizStyle(option.style)}
                  >
                    {option.icon}
                  </SegmentedIconButton>
                ))}
              </SegmentedIconGroup>
            )}
            <SegmentedIconGroup label="预览视图">
              <SegmentedIconButton
                selected={!showTranscript}
                label="语音可视化视图"
                onClick={() => setShowTranscript(false)}
              >
                <Mic size={14} />
              </SegmentedIconButton>
              <SegmentedIconButton
                selected={showTranscript}
                label="文字聊天记录视图"
                onClick={() => setShowTranscript(true)}
              >
                <MessageSquareText size={14} />
              </SegmentedIconButton>
            </SegmentedIconGroup>
          </div>
        )}
      </div>

      <DebugVoicePanel
        showTranscript={showTranscript}
        vizStyle={vizStyle}
        assistantId={assistantId}
      />
    </aside>
  );
}

function DebugVoicePanel({
  showTranscript,
  vizStyle,
  assistantId,
}: {
  showTranscript: boolean;
  vizStyle: VizStyle;
  assistantId: string | null;
}) {
  const {
    status,
    error,
    micWarning,
    localStream,
    remoteStream,
    messages,
    audioInputs,
    selectedDeviceId,
    selectDevice,
    sendText,
    connect,
    disconnect,
    audioRef,
  } = useVoicePreview(assistantId);
  // 连接中或已连通都视作"会话进行中"
  const recording = status === "connecting" || status === "connected";
  const [textDraft, setTextDraft] = useState("");

  function handleSendText() {
    if (sendText(textDraft)) {
      setTextDraft("");
    }
  }

  return (
    <div className="flex min-h-0 flex-1 flex-col">
      {/* 后端 TTS 音频经 WebRTC 媒体流过来,挂这里播放 */}
      <audio ref={audioRef} autoPlay playsInline className="hidden" />
      {!SHOW_VOICE_VIZ || showTranscript ? (
        <>
          <DebugTranscriptPanel messages={messages} recording={recording} />
          <VoiceSessionControls
            status={status}
            error={error}
            micWarning={micWarning}
            assistantId={assistantId}
            audioInputs={audioInputs}
            selectedDeviceId={selectedDeviceId}
            selectDevice={selectDevice}
            connect={connect}
            disconnect={disconnect}
          />
        </>
      ) : (
        <div className="relative flex min-h-0 flex-1 flex-col items-center justify-center gap-3 overflow-y-auto px-6 py-3 text-center">
          <div
            className="pointer-events-none absolute left-1/2 top-2 h-72 w-72 -translate-x-1/2 rounded-full opacity-50 blur-3xl"
            style={{
              background:
                "radial-gradient(circle, color-mix(in srgb, var(--gradient-sky) 42%, transparent), color-mix(in srgb, var(--gradient-lavender) 18%, transparent) 48%, transparent 74%)",
            }}
          />

          <Badge
            variant="secondary"
            className="relative gap-1.5 rounded-full border border-hairline bg-canvas-soft px-3 py-1 text-[11px] font-medium text-muted-foreground shadow-none"
          >
            <span
              className={[
                "h-1.5 w-1.5 rounded-full",
                recording ? "animate-pulse bg-success" : "bg-muted-soft",
              ].join(" ")}
            />
            {recording ? "会话进行中" : "准备开始"}
          </Badge>

          <div className="relative flex h-[200px] w-[240px] shrink-0 items-center justify-center">
            {(() => {
              const shared = {
                active: Boolean(localStream),
                stream: localStream,
                className: "relative shrink-0",
              } as const;
              if (vizStyle === "aura")
                return <AuraVisualizer {...shared} size={200} />;
              if (vizStyle === "nebula")
                return <NebulaVisualizer {...shared} size={200} />;
              if (vizStyle === "bars")
                return <SpectrumVisualizer {...shared} size={200} />;
              return <WaveVisualizer {...shared} size={200} />;
            })()}
          </div>

          <div className="relative max-w-xs space-y-1.5">
            <div className="font-display display-sm text-foreground">
              {status === "connecting"
                ? "连接中…"
                : status === "connected"
                  ? micWarning
                    ? "仅收听模式"
                    : "我在聆听"
                  : "开始一次语音对话"}
            </div>
            <p className="mx-auto text-xs leading-5 text-muted-foreground">
              {status === "failed"
                  ? error ||
                    "连接失败，请确认后端已启动且助手已保存后重试。"
                  : !assistantId
                    ? "请先保存助手，再开始语音预览。"
                    : micWarning
                      ? `${micWarning} 可接收助手播报，但无法发送语音。`
                    : recording
                      ? "直接说话即可。助手会在您停顿后自然回应。"
                      : "测试语音识别、响应速度与助手的播报效果。"}
            </p>
          </div>

          <div className="relative w-full max-w-xs">
            <Select
              value={selectedDeviceId || "default"}
              onValueChange={(value) =>
                selectDevice(value === "default" ? "" : value)
              }
            >
              <SelectTrigger
                size="sm"
                className="w-full justify-center gap-2 rounded-full border-hairline bg-canvas-soft text-xs text-muted-foreground"
                aria-label="选择麦克风"
              >
                <Mic size={13} className="shrink-0 text-muted-soft" />
                <SelectValue placeholder="默认麦克风" />
              </SelectTrigger>
              <SelectContent>
                <SelectItem value="default">默认麦克风</SelectItem>
                {audioInputs.map((device, index) => (
                  <SelectItem key={device.deviceId} value={device.deviceId}>
                    {device.label || `麦克风 ${index + 1}`}
                  </SelectItem>
                ))}
              </SelectContent>
            </Select>
          </div>

          <Button
            disabled={!assistantId || status === "connecting"}
            onClick={() => {
              if (recording) {
                disconnect();
              } else {
                void connect();
              }
            }}
            className={[
              "relative h-11 gap-2 rounded-full px-6 text-sm font-medium shadow-sm transition-transform hover:scale-[1.03]",
              recording
                ? "bg-destructive text-white hover:bg-destructive/90"
                : "",
            ].join(" ")}
            aria-label={recording ? "结束语音测试" : "开始语音测试"}
          >
            {status === "connecting" ? (
              <Loader2 size={18} className="animate-spin" />
            ) : recording ? (
              <PhoneOff size={18} />
            ) : (
              <Mic size={18} />
            )}
            {recording ? "结束对话" : "开始对话"}
          </Button>
        </div>
      )}

      <div className="shrink-0 border-t border-hairline bg-card p-3">
        <div className="flex items-end gap-2">
          <Textarea
            rows={1}
            value={textDraft}
            disabled={status !== "connected"}
            onChange={(event) => setTextDraft(event.target.value)}
            onKeyDown={(event) => {
              if (event.key === "Enter" && !event.shiftKey && !event.nativeEvent.isComposing) {
                event.preventDefault();
                handleSendText();
              }
            }}
            placeholder={
              status === "connected"
                ? "输入文字发送给助手，将打断当前播报…"
                : "开始对话后可输入文字…"
            }
            className="max-h-24 min-h-10 flex-1 resize-none border-hairline-strong bg-background text-sm text-foreground placeholder:text-muted-soft"
          />
          <Button
            size="icon"
            className="shrink-0"
            aria-label="发送调试消息"
            disabled={status !== "connected" || !textDraft.trim()}
            onClick={handleSendText}
          >
            <Send size={16} />
          </Button>
        </div>
      </div>

      {/* 底部双轨波形:用户麦克风 + 助手音频,可折叠 */}
      <WaveformTimelinePanel
        userStream={localStream}
        agentStream={remoteStream}
        active={status === "connected"}
      />
    </div>
  );
}

// 会话控制条:状态 + 麦克风选择 + 开始/结束按钮。
// 原本这些控件在中央可视化视图里,可视化隐藏后(SHOW_VOICE_VIZ=false)集中到这一条。
function VoiceSessionControls({
  status,
  error,
  micWarning,
  assistantId,
  audioInputs,
  selectedDeviceId,
  selectDevice,
  connect,
  disconnect,
}: {
  status: VoicePreviewStatus;
  error: string | null;
  micWarning: string | null;
  assistantId: string | null;
  audioInputs: MediaDeviceInfo[];
  selectedDeviceId: string;
  selectDevice: (deviceId: string) => void;
  connect: () => Promise<void>;
  disconnect: () => void;
}) {
  const recording = status === "connecting" || status === "connected";
  const hint =
    status === "failed"
      ? error || "连接失败，请确认后端已启动且助手已保存后重试。"
      : !assistantId
        ? "请先保存助手，再开始语音预览。"
        : micWarning
          ? `${micWarning} 可接收助手播报，但无法发送语音。`
          : null;

  return (
    <div className="shrink-0 border-t border-hairline px-3 py-2.5">
      <div className="flex items-center gap-2">
        <span
          className={[
            "h-1.5 w-1.5 shrink-0 rounded-full",
            recording
              ? "animate-pulse bg-success"
              : status === "failed"
                ? "bg-destructive"
                : "bg-muted-soft",
          ].join(" ")}
        />
        <span className="shrink-0 text-xs text-muted-foreground">
          {status === "connecting"
            ? "连接中…"
            : status === "connected"
              ? micWarning
                ? "仅收听"
                : "进行中"
              : status === "failed"
                ? "连接失败"
                : "准备开始"}
        </span>

        <Select
          value={selectedDeviceId || "default"}
          onValueChange={(value) =>
            selectDevice(value === "default" ? "" : value)
          }
        >
          <SelectTrigger
            size="sm"
            className="min-w-0 flex-1 gap-2 rounded-full border-hairline bg-canvas-soft text-xs text-muted-foreground"
            aria-label="选择麦克风"
          >
            <Mic size={13} className="shrink-0 text-muted-soft" />
            <SelectValue placeholder="默认麦克风" />
          </SelectTrigger>
          <SelectContent>
            <SelectItem value="default">默认麦克风</SelectItem>
            {audioInputs.map((device, index) => (
              <SelectItem key={device.deviceId} value={device.deviceId}>
                {device.label || `麦克风 ${index + 1}`}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>

        <Button
          size="sm"
          disabled={!assistantId || status === "connecting"}
          onClick={() => {
            if (recording) {
              disconnect();
            } else {
              void connect();
            }
          }}
          className={[
            "shrink-0 gap-1.5 rounded-full",
            recording ? "bg-destructive text-white hover:bg-destructive/90" : "",
          ].join(" ")}
          aria-label={recording ? "结束语音测试" : "开始语音测试"}
        >
          {status === "connecting" ? (
            <Loader2 size={14} className="animate-spin" />
          ) : recording ? (
            <PhoneOff size={14} />
          ) : (
            <Mic size={14} />
          )}
          {recording ? "结束对话" : "开始对话"}
        </Button>
      </div>

      {hint && (
        <p className="mt-1.5 text-xs leading-5 text-muted-foreground">{hint}</p>
      )}
    </div>
  );
}

// ISO 时间戳 → HH:MM(本地时区),解析失败返回空串
function formatMessageTime(iso: string): string {
  const d = new Date(iso);
  if (Number.isNaN(d.getTime())) return "";
  const pad = (n: number) => String(n).padStart(2, "0");
  return `${pad(d.getHours())}:${pad(d.getMinutes())}`;
}

function DebugTranscriptPanel({
  messages,
  recording,
}: {
  messages: ChatMessage[];
  recording: boolean;
}) {
  const scrollRef = useRef<HTMLDivElement>(null);

  // 新消息时滚到底部
  useEffect(() => {
    const el = scrollRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [messages]);

  if (messages.length === 0) {
    return (
      <div className="flex min-h-0 flex-1 flex-col items-center justify-center gap-2 px-6 text-center">
        <MessageSquareText size={28} className="text-muted-soft" />
        <div className="text-sm font-medium text-foreground">
          {recording ? "暂无聊天记录" : "尚未开始对话"}
        </div>
        <p className="max-w-xs text-xs leading-5 text-muted-foreground">
          {recording
            ? "开口说话或在下方输入文字，对话内容会实时显示在这里。"
            : "点击「开始对话」后，语音与文字消息会实时显示在这里。"}
        </p>
      </div>
    );
  }

  return (
    <div
      ref={scrollRef}
      className="flex min-h-0 flex-1 flex-col overflow-y-auto px-5 py-4"
    >
      <div className="flex flex-col gap-4">
        {messages.map((message) => {
          const time = formatMessageTime(message.timestamp);
          return message.role === "assistant" ? (
            <div
              key={message.id}
              className="flex max-w-[88%] flex-col gap-1 self-start"
            >
              <span className="px-1 text-[11px] text-muted-soft">
                助手{time ? ` · ${time}` : ""}
              </span>
              <div className="whitespace-pre-wrap rounded-2xl rounded-tl-sm bg-surface-strong px-4 py-2.5 text-sm leading-6 text-foreground">
                {message.content}
              </div>
            </div>
          ) : (
            <div
              key={message.id}
              className="flex max-w-[88%] flex-col items-end gap-1 self-end"
            >
              <span className="px-1 text-[11px] text-muted-soft">
                我{time ? ` · ${time}` : ""}
              </span>
              <div className="whitespace-pre-wrap rounded-2xl rounded-tr-sm bg-primary px-4 py-2.5 text-sm leading-6 text-primary-foreground">
                {message.content}
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

function EditorBackButton({ onClick }: { onClick: () => void }) {
  return (
    <Button
      variant="ghost"
      size="icon"
      className="shrink-0 text-muted-foreground hover:text-foreground"
      onClick={onClick}
      aria-label="返回助手列表"
    >
      <ChevronLeft size={20} />
    </Button>
  );
}

function AssistantIdentity({ assistantId }: { assistantId: string | null }) {
  const [copied, setCopied] = useState(false);

  async function copyId() {
    if (!assistantId) return;
    await navigator.clipboard.writeText(assistantId);
    setCopied(true);
    window.setTimeout(() => setCopied(false), 1600);
  }

  return (
    <div className="ml-1 flex shrink-0 items-center rounded-full border border-hairline bg-canvas-soft pl-2.5 text-[11px] text-muted-foreground">
      <span className="font-mono">
        {assistantId ? `ID · ${assistantId}` : "ID · 保存后生成"}
      </span>
      {assistantId && (
        <button
          type="button"
          onClick={() => void copyId()}
          className="ml-1 flex h-7 w-7 items-center justify-center rounded-full text-muted-soft transition-colors hover:bg-surface-strong hover:text-foreground"
          aria-label={copied ? "助手 ID 已复制" : "复制助手 ID"}
          title={copied ? "已复制" : "复制 ID"}
        >
          {copied ? <Check size={13} /> : <Copy size={13} />}
        </button>
      )}
      {!assistantId && <span className="h-7 w-2" aria-hidden />}
    </div>
  );
}

function EditableTitle({
  value,
  onChange,
}: {
  value: string;
  onChange: (value: string) => void;
}) {
  const [editing, setEditing] = useState(false);
  const [draft, setDraft] = useState(value);
  const inputRef = useRef<HTMLInputElement>(null);

  useEffect(() => {
    if (editing) {
      inputRef.current?.focus();
      inputRef.current?.select();
    }
  }, [editing]);

  function startEdit() {
    setDraft(value);
    setEditing(true);
  }

  function commit() {
    const next = draft.trim();
    if (next) {
      onChange(next);
    }
    setEditing(false);
  }

  if (editing) {
    return (
      <input
        ref={inputRef}
        value={draft}
        onChange={(event) => setDraft(event.target.value)}
        onBlur={commit}
        onKeyDown={(event) => {
          if (event.key === "Enter") {
            event.preventDefault();
            commit();
          } else if (event.key === "Escape") {
            event.preventDefault();
            setEditing(false);
          }
        }}
        className="font-display display-sm w-[min(60vw,420px)] border-b border-primary bg-transparent text-ink outline-none"
      />
    );
  }

  return (
    <button
      type="button"
      onClick={startEdit}
      title="点击修改助手名称"
      className="group -mx-2 flex min-w-0 items-center gap-2 rounded-lg px-2 py-1 text-left transition-colors hover:bg-surface-strong"
    >
      <span className="font-display display-sm truncate text-ink">
        {value || "未命名助手"}
      </span>
      <Pencil
        size={16}
        className="shrink-0 text-muted-soft opacity-0 transition-opacity group-hover:opacity-100"
      />
    </button>
  );
}

function HelpHint({ text }: { text: string }) {
  return (
    <Popover>
      <PopoverTrigger asChild>
        <button
          type="button"
          aria-label="查看说明"
          onClick={(event) => event.stopPropagation()}
          className="flex h-5 w-5 items-center justify-center rounded-full text-muted-soft transition-colors hover:bg-surface-strong hover:text-foreground"
        >
          <HelpCircle size={14} />
        </button>
      </PopoverTrigger>
      <PopoverContent
        align="start"
        className="w-72 text-sm leading-6 text-muted-foreground"
      >
        {text}
      </PopoverContent>
    </Popover>
  );
}

function SectionCard({
  icon,
  title,
  description,
  children,
}: {
  icon?: React.ReactNode;
  title?: string;
  description?: string;
  children: React.ReactNode;
}) {
  const hasHeader = Boolean(title);

  return (
    <Card className="rounded-2xl border-hairline bg-card text-card-foreground shadow-sm">
      {hasHeader && (
        <CardHeader>
          <div className="flex items-center gap-3">
            {icon && (
              <div className="flex h-10 w-10 items-center justify-center rounded-full bg-surface-strong text-foreground">
                {icon}
              </div>
            )}

            <div className="flex items-center gap-1.5">
              <CardTitle className="text-base font-medium">{title}</CardTitle>
              {description && <HelpHint text={description} />}
            </div>
          </div>
        </CardHeader>
      )}

      <CardContent className={hasHeader ? "space-y-4" : undefined}>
        {children}
      </CardContent>
    </Card>
  );
}

function InputField({
  label,
  value,
  placeholder,
  type = "text",
  onChange,
}: {
  label?: string;
  value: string;
  placeholder?: string;
  type?: string;
  onChange: (value: string) => void;
}) {
  return (
    <label className="block">
      {label && (
        <div className="mb-2 text-sm font-medium text-foreground">{label}</div>
      )}
      <Input
        value={value}
        type={type}
        placeholder={placeholder}
        onChange={(event) => onChange(event.target.value)}
        className="border-hairline-strong bg-background text-foreground placeholder:text-muted-soft"
      />
    </label>
  );
}

function SecretInputField({
  label,
  value,
  placeholder,
  storedValueMask,
  onChange,
}: {
  label?: string;
  value: string;
  placeholder?: string;
  storedValueMask: string;
  onChange: (value: string) => void;
}) {
  const [showValue, setShowValue] = useState(false);
  const hasStoredValue = Boolean(storedValueMask);

  return (
    <label className="block">
      {label && (
        <div className="mb-2 text-sm font-medium text-foreground">{label}</div>
      )}
      {hasStoredValue && (
        <div className="mb-2 flex items-center gap-2 text-xs text-muted-foreground">
          <span>当前密钥</span>
          <code className="rounded-md bg-surface-strong px-2 py-1 font-mono text-foreground">
            {storedValueMask}
          </code>
        </div>
      )}
      <div className="relative">
        <Input
          value={value}
          type={showValue ? "text" : "password"}
          placeholder={
            hasStoredValue ? "已配置，留空则保持不变" : placeholder
          }
          autoComplete="new-password"
          onChange={(event) => {
            const nextValue = event.target.value;
            if (!nextValue) setShowValue(false);
            onChange(nextValue);
          }}
          className="border-hairline-strong bg-background pr-10 text-foreground placeholder:text-muted-soft"
        />
        <button
          type="button"
          disabled={!value}
          onClick={() => setShowValue((current) => !current)}
          className="absolute right-3 top-1/2 -translate-y-1/2 text-muted-soft transition-colors hover:text-foreground disabled:cursor-not-allowed disabled:opacity-40"
          aria-label={showValue ? "隐藏 API Key" : "显示 API Key"}
        >
          {showValue ? <EyeOff size={16} /> : <Eye size={16} />}
        </button>
      </div>
      {hasStoredValue && (
        <p className="mt-2 text-xs leading-5 text-muted-foreground">
          仅显示当前密钥首尾用于识别。留空可保持原密钥，输入新值将覆盖原密钥。
        </p>
      )}
    </label>
  );
}

function TextAreaField({
  label,
  value,
  placeholder,
  rows = 4,
  onChange,
}: {
  label?: string;
  value: string;
  placeholder?: string;
  rows?: number;
  onChange: (value: string) => void;
}) {
  return (
    <label className="block">
      {label && (
        <div className="mb-2 text-sm font-medium text-foreground">{label}</div>
      )}
      <Textarea
        value={value}
        placeholder={placeholder}
        onChange={(event) => onChange(event.target.value)}
        rows={rows}
        className="resize-none border-hairline-strong bg-background text-foreground placeholder:text-muted-soft"
      />
    </label>
  );
}

// Radix Select 不允许空字符串 value,用哨兵表示"未选/无"
const NONE_VALUE = "__none__";

function ResourceSelectField({
  label,
  value,
  options,
  noneLabel,
  onChange,
}: {
  label?: string;
  value: string;
  options: { value: string; label: string }[];
  /** 提供则在顶部加一个"无/默认"选项,选中映射为空串 */
  noneLabel?: string;
  onChange: (value: string) => void;
}) {
  return (
    <div className="block">
      {label && (
        <div className="mb-2 text-sm font-medium text-foreground">{label}</div>
      )}

      <Select
        value={value || NONE_VALUE}
        onValueChange={(v) => onChange(v === NONE_VALUE ? "" : v)}
      >
        <SelectTrigger className="w-full border-hairline-strong bg-background text-foreground">
          <SelectValue placeholder={label ? `请选择${label}` : "请选择"} />
        </SelectTrigger>

        <SelectContent className="border-hairline bg-popover text-popover-foreground">
          {noneLabel && (
            <SelectItem value={NONE_VALUE}>{noneLabel}</SelectItem>
          )}
          {options.map((item) => (
            <SelectItem key={item.value} value={item.value}>
              {item.label}
            </SelectItem>
          ))}
        </SelectContent>
      </Select>
    </div>
  );
}

function ToggleRow({
  title,
  description,
  checked,
  onChange,
}: {
  title: string;
  description: string;
  checked: boolean;
  onChange: (checked: boolean) => void;
}) {
  return (
    <div className="flex items-center justify-between rounded-xl border border-hairline bg-canvas-soft p-4">
      <div>
        <div className="font-medium text-foreground">{title}</div>
        <div className="mt-1 text-sm text-muted-foreground">{description}</div>
      </div>
      <Switch checked={checked} onCheckedChange={onChange} />
    </div>
  );
}
