import { useState, useEffect, useCallback } from 'react';
import {
  Settings,
  Save,
  X,
  Plus,
  Trash2,
  AlertTriangle,
  Loader2,
  Wifi,
  CheckCircle,
  RefreshCw,
  UserCheck,
  Pencil,
  Bot,
} from 'lucide-react';

// ── Extracted modules ─────────────────────────────────
import type {
  AppConfig,
  ConfigPageProps,
  TabId,
  ProviderConfig,
  ModelConfig,
  AgentConfig,
  McpServerConfig,
  McpStatusResponse,
  SkillListResponse,
  SubagentListResponse,
  SubagentItem,
  ToolsListResponse,
  ExpertItem,
  ExpertListResponse,
  CapabilityPolicy,
  KnownSource,
  SchedulerConfig,
  ChannelConfig,
  ModelOptionsResponse,
} from './types';
import { TABS, inputCls, selectCls, TIMEZONE_OPTIONS } from './constants';
import {
  Field,
  Toggle,
  TagEditor,
  SectionCard,
  SourceEditor,
  MapEntryHeader,
  CheckboxList,
  ModalHeader,
  ModalFooter,
} from './ui';
import { getAppConfig, updateAppConfig, restartGateway, checkHealth } from '../../api/config';
import { listSkills, toggleSkill } from '../../api/skills';
import { listTools } from '../../api/tools';
import { listSubagents, toggleSubagent, updateSubagent } from '../../api/subagents';
import {
  listExperts,
  toggleExpert,
  createExpert,
  updateExpert,
  deleteExpert,
  listModelOptions,
} from '../../api/experts';
import { getMcpStatus } from '../../api/mcp';
export { getSelectedExpert, selectExpert } from '../../api/experts';

// ── Main Component ─────────────────────────────────────
export function ConfigPage({ onClose, onSaveConnection, initialTab }: ConfigPageProps) {
  const [config, setConfig] = useState<AppConfig | null>(null);
  const [activeTab, setActiveTab] = useState<TabId>(initialTab ?? 'providers');
  const [loading, setLoading] = useState(true);
  // Connection settings (localStorage-based)
  const [connHost, setConnHost] = useState(() => {
    try {
      return localStorage.getItem('picobot-gateway-host') || '127.0.0.1';
    } catch {
      return '127.0.0.1';
    }
  });
  const [connPort, setConnPort] = useState(() => {
    try {
      const p = parseInt(localStorage.getItem('picobot-gateway-port') || '19876', 10);
      return isNaN(p) ? 19876 : p;
    } catch {
      return 19876;
    }
  });
  const [connError, setConnError] = useState('');

  const [saving, setSaving] = useState(false);
  const [error, setError] = useState('');
  const [toast, setToast] = useState('');
  const [dirty, setDirty] = useState(false);
  const [showRestartDialog, setShowRestartDialog] = useState(false);
  const [restarting, setRestarting] = useState(false);
  const [mcpStatus, setMcpStatus] = useState<McpStatusResponse | null>(null);
  const [skillList, setSkillList] = useState<SkillListResponse | null>(null);
  const [skillListLoading, setSkillListLoading] = useState(false);
  const [toolList, setToolList] = useState<ToolsListResponse | null>(null);
  const [toolListLoading, setToolListLoading] = useState(false);
  const [subagentList, setSubagentList] = useState<SubagentListResponse | null>(null);
  const [subagentListLoading, setSubagentListLoading] = useState(false);
  const [expertList, setExpertList] = useState<ExpertListResponse | null>(null);
  const [expertListLoading, setExpertListLoading] = useState(false);
  const [modelOptions, setModelOptions] = useState<ModelOptionsResponse | null>(null);
  const [editingExpert, setEditingExpert] = useState<{
    mode: 'create' | 'edit';
    name?: string;
    scope: string;
    nameField: string;
    description: string;
    body: string;
    provider: string;
    model: string;
    allowedSkills: string[];
    deniedSkills: string[];
    allowedTools: string[];
    deniedTools: string[];
    allowedSubagents: string[];
    deniedSubagents: string[];
  } | null>(null);
  const [editingExpertError, setEditingExpertError] = useState('');
  const [savingExpert, setSavingExpert] = useState(false);
  const [editingSubagent, setEditingSubagent] = useState<{
    name: string;
    description: string;
    provider: string;
    model: string;
    allowedSkills: string[];
    deniedSkills: string[];
    allowedTools: string[];
    deniedTools: string[];
    allowedSubagents: string[];
    deniedSubagents: string[];
  } | null>(null);
  const [editingSubagentError, setEditingSubagentError] = useState('');
  const [savingSubagent, setSavingSubagent] = useState(false);

  const fetchMcpStatus = useCallback(async () => {
    const data = await getMcpStatus();
    if (data) setMcpStatus(data);
  }, []);

  const fetchSkillList = useCallback(async () => {
    setSkillListLoading(true);
    const data = await listSkills();
    if (data) setSkillList(data);
    setSkillListLoading(false);
  }, []);

  const fetchToolList = useCallback(async () => {
    setToolListLoading(true);
    const data = await listTools();
    if (data) setToolList(data);
    setToolListLoading(false);
  }, []);

  const toggleSkillCb = useCallback(async (name: string, scope: string, enabled: boolean) => {
    return toggleSkill(name, scope, enabled);
  }, []);

  const fetchSubagentList = useCallback(async () => {
    setSubagentListLoading(true);
    const data = await listSubagents();
    if (data) setSubagentList(data);
    setSubagentListLoading(false);
  }, []);

  const toggleSubagentCb = useCallback(async (name: string, scope: string, enabled: boolean) => {
    return toggleSubagent(name, scope, enabled);
  }, []);

  const updateSubagentCb = useCallback(
    async (payload: {
      name: string;
      description?: string;
      body?: string;
      capability?: CapabilityPolicy;
      provider?: string;
      model?: string;
    }) => {
      return updateSubagent(payload);
    },
    [],
  );

  const fetchExpertList = useCallback(async () => {
    setExpertListLoading(true);
    const data = await listExperts();
    if (data) setExpertList(data);
    setExpertListLoading(false);
  }, []);

  const toggleExpertCb = useCallback(async (name: string, scope: string, enabled: boolean) => {
    return toggleExpert(name, scope, enabled);
  }, []);

  const createExpertCb = useCallback(
    async (payload: {
      name: string;
      description: string;
      body: string;
      scope: string;
      capability?: CapabilityPolicy;
      provider?: string;
      model?: string;
    }) => {
      return createExpert(payload);
    },
    [],
  );

  const updateExpertCb = useCallback(
    async (payload: {
      name: string;
      scope: string;
      description?: string;
      body?: string;
      capability?: CapabilityPolicy;
      provider?: string;
      model?: string;
    }) => {
      return updateExpert(payload);
    },
    [],
  );

  const deleteExpertCb = useCallback(async (name: string, scope: string) => {
    return deleteExpert(name, scope);
  }, []);

  const handleClose = useCallback(() => {
    if (dirty && !confirm('有未保存的更改，确定要关闭吗？')) return;
    onClose();
  }, [dirty, onClose]);

  // Load config
  useEffect(() => {
    getAppConfig().then(([data, err]) => {
      if (data) setConfig(data);
      if (err) setError('加载配置失败: ' + err);
      setLoading(false);
    });
  }, []);

  // Fetch MCP status when MCP tab is selected
  useEffect(() => {
    if (activeTab === 'mcp') fetchMcpStatus();
  }, [activeTab, fetchMcpStatus]);

  // Fetch skill list when skills tab is selected
  useEffect(() => {
    if (activeTab === 'skills') fetchSkillList();
  }, [activeTab, fetchSkillList]);

  // Fetch subagent list when subagents tab is selected
  useEffect(() => {
    if (activeTab === 'subagents') fetchSubagentList();
  }, [activeTab, fetchSubagentList]);

  // Fetch skills + tools + model options when experts/subagents tab is selected (for capability CheckboxList & provider/model dropdowns)
  useEffect(() => {
    if (activeTab === 'experts' || activeTab === 'subagents') {
      if (!skillList) fetchSkillList();
      if (!toolList) fetchToolList();
      if (!modelOptions)
        listModelOptions().then((data) => {
          if (data) setModelOptions(data);
        });
    }
  }, [activeTab, fetchSkillList, fetchToolList, skillList, toolList, modelOptions]);

  // experts tab 编辑专家时也需要子代理勾选列表，按需加载（subagents tab 由下方独立 useEffect 刷新）
  useEffect(() => {
    if (activeTab === 'experts' && !subagentList) fetchSubagentList();
  }, [activeTab, fetchSubagentList, subagentList]);

  // Fetch expert list when experts tab is selected
  useEffect(() => {
    if (activeTab === 'experts') fetchExpertList();
  }, [activeTab, fetchExpertList]);

  // ESC to close
  useEffect(() => {
    const h = (e: KeyboardEvent) => {
      if (e.key === 'Escape') handleClose();
    };
    document.addEventListener('keydown', h);
    return () => document.removeEventListener('keydown', h);
  }, [handleClose]);

  // 子模态框打开时，ESC 键仅关闭子模态框（阻止冒泡到 ConfigPage 全局 ESC，避免关闭整个配置页）
  // 必须放在所有条件 return 之前，否则 loading 首次渲染时不执行此 hook，
  // config 加载后重新渲染才执行 → hooks 数量不一致 → React 崩溃
  useEffect(() => {
    if (!editingExpert && !editingSubagent) return;
    const handler = (e: KeyboardEvent) => {
      if (e.key === 'Escape') {
        e.stopPropagation();
        e.preventDefault();
        setEditingExpert(null);
        setEditingSubagent(null);
      }
    };
    window.addEventListener('keydown', handler, true);
    return () => window.removeEventListener('keydown', handler, true);
  }, [editingExpert, editingSubagent]);

  const update = useCallback(<K extends keyof AppConfig>(key: K, value: AppConfig[K]) => {
    setConfig((prev) => (prev ? { ...prev, [key]: value } : prev));
    setDirty(true);
  }, []);

  const handleSave = async () => {
    if (!config) return;
    setSaving(true);
    setError('');
    const [ok, err] = await updateAppConfig(config);
    if (!ok) {
      setError(err || '保存失败');
    } else {
      // Config is now synced to both disk and in-memory state,
      // so the local state is already correct. No need to re-fetch.
      setDirty(false);
      // Show restart confirmation dialog
      setShowRestartDialog(true);
    }
    setSaving(false);
  };

  const handleRestart = async () => {
    setShowRestartDialog(false);
    setRestarting(true);
    try {
      const { status, data } = await restartGateway();
      if (status === 409) {
        setToast(data.message || '有任务运行中，请等待完成后再试');
        setRestarting(false);
        setTimeout(() => setToast(''), 5000);
        return;
      }
      if (status < 200 || status >= 300) throw new Error(data.message || '重启失败');
      setToast('服务正在重启，页面将自动重连...');
      // Poll /health until gateway is back
      const poll = async () => {
        for (let i = 0; i < 30; i++) {
          await new Promise((r) => setTimeout(r, 1000));
          if (await checkHealth()) {
            const [refreshed] = await getAppConfig();
            if (refreshed) setConfig(refreshed);
            setToast('服务已重启，配置已生效');
            setRestarting(false);
            setTimeout(() => setToast(''), 3000);
            return;
          }
        }
        setToast('重启超时，请手动刷新页面');
        setRestarting(false);
        setTimeout(() => setToast(''), 5000);
      };
      poll();
    } catch (e: unknown) {
      setError(e instanceof Error ? e.message : '重启失败');
      setRestarting(false);
    }
  };

  // ── Render sections ──────────────────────────────────
  if (loading)
    return (
      <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
        <Loader2 className="h-8 w-8 text-[var(--accent-cyan)] animate-spin" />
      </div>
    );

  if (!config)
    return (
      <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm">
        <div className="text-red-400 text-center space-y-3">
          <AlertTriangle className="h-10 w-10 mx-auto" />
          <p>{error || '加载失败'}</p>
          <button
            onClick={onClose}
            className="px-4 py-2 rounded-lg bg-[var(--bg-tertiary)] text-sm"
          >
            关闭
          </button>
        </div>
      </div>
    );

  const handleSaveConnection = () => {
    const host = connHost.trim();
    if (!host) {
      setConnError('主机地址不能为空');
      return;
    }
    if (connPort < 1 || connPort > 65535) {
      setConnError('端口号必须在 1-65535 之间');
      return;
    }
    setConnError('');
    localStorage.setItem('picobot-gateway-host', host);
    localStorage.setItem('picobot-gateway-port', String(connPort));
    onSaveConnection?.(host, connPort);
    setToast('连接设置已保存，正在重连...');
    setTimeout(() => setToast(''), 3000);
  };

  const renderConnection = () => (
    <div className="space-y-5">
      <SectionCard title="WebSocket 连接">
        <Field label="主机地址">
          <input
            value={connHost}
            onChange={(e) => {
              setConnHost(e.target.value);
              setConnError('');
            }}
            className={inputCls}
            placeholder="127.0.0.1"
          />
        </Field>
        <Field label="端口号">
          <input
            type="number"
            value={connPort}
            onChange={(e) => {
              setConnPort(+e.target.value);
              setConnError('');
            }}
            min={1}
            max={65535}
            className={inputCls}
            placeholder="19876"
          />
        </Field>
        {connError && (
          <div className="text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-lg px-3 py-2">
            {connError}
          </div>
        )}
        <div className="text-xs text-[var(--text-muted)] bg-[var(--overlay-dim)] rounded-lg px-3 py-2 font-mono">
          ws://{connHost.trim() || '...'}:{connPort || '...'}/ws
        </div>
      </SectionCard>
      <button
        onClick={handleSaveConnection}
        className="flex items-center gap-2 px-5 py-2.5 rounded-xl text-sm font-medium text-white bg-[var(--accent-cyan)]/20 border border-[var(--accent-cyan)]/30 hover:bg-[var(--accent-cyan)]/30 transition-all"
      >
        <Wifi className="h-4 w-4" /> 保存并重连
      </button>
    </div>
  );

  const renderGateway = () => (
    <div className="space-y-5">
      <SectionCard title="连接">
        <Field label="主机地址">
          <input
            value={config.gateway.host}
            onChange={(e) => update('gateway', { ...config.gateway, host: e.target.value })}
            className={inputCls}
          />
        </Field>
        <Field label="端口">
          <input
            type="number"
            value={config.gateway.port}
            onChange={(e) => update('gateway', { ...config.gateway, port: +e.target.value })}
            className={inputCls}
          />
        </Field>
      </SectionCard>
      <SectionCard title="行为">
        <div className="flex items-center justify-between">
          <span className="text-sm text-[var(--text-secondary)]">显示工具结果</span>
          <Toggle
            checked={config.gateway.show_tool_results}
            onChange={(v) => update('gateway', { ...config.gateway, show_tool_results: v })}
          />
        </div>
        <Field label="Agent Prompt 重新注入间隔" hint="每多少轮对话重新注入系统提示">
          <input
            type="number"
            value={config.gateway.agent_prompt_reinject_every}
            onChange={(e) =>
              update('gateway', { ...config.gateway, agent_prompt_reinject_every: +e.target.value })
            }
            className={inputCls}
          />
        </Field>
        <Field label="最大并发请求数">
          <input
            type="number"
            value={config.gateway.max_concurrent_requests}
            onChange={(e) =>
              update('gateway', { ...config.gateway, max_concurrent_requests: +e.target.value })
            }
            className={inputCls}
          />
        </Field>
        <Field label="Session TTL (小时)" hint="留空表示不过期">
          <input
            type="number"
            value={config.gateway.session_ttl_hours ?? ''}
            onChange={(e) => {
              const v = e.target.value;
              update('gateway', { ...config.gateway, session_ttl_hours: v ? +v : undefined });
            }}
            className={inputCls}
            placeholder="24"
          />
        </Field>
      </SectionCard>
    </div>
  );

  const renderProviders = () => {
    const entries = Object.entries(config.providers);
    const addProvider = () => {
      const name = prompt('Provider 名称:')?.trim();
      if (name && !config.providers[name]) {
        update('providers', {
          ...config.providers,
          [name]: {
            type: 'openai',
            base_url: '',
            api_key: '',
            extra_headers: {},
            llm_timeout_secs: 120,
            memory_maintenance_timeout_secs: 600,
          },
        });
      }
    };
    const delProvider = (name: string) => {
      if (confirm(`删除 Provider "${name}"?`)) {
        const { [name]: _, ...rest } = config.providers;
        update('providers', rest);
      }
    };
    const renameProvider = (oldName: string, newName: string) => {
      if (newName === oldName || !newName) return;
      const entries = Object.entries(config.providers);
      const newMap: Record<string, ProviderConfig> = {};
      for (const [k, v] of entries) {
        newMap[k === oldName ? newName : k] = v;
      }
      update('providers', newMap);
    };
    const updProvider = (name: string, patch: Partial<ProviderConfig>) => {
      update('providers', { ...config.providers, [name]: { ...config.providers[name], ...patch } });
    };
    return (
      <div className="space-y-4">
        {entries.map(([name, p]) => (
          <div
            key={name}
            className="rounded-xl border border-[var(--border-color)] bg-[var(--bg-secondary)]/60 overflow-hidden"
          >
            <MapEntryHeader
              name={name}
              onDelete={() => delProvider(name)}
              onRename={(n) => renameProvider(name, n)}
            />
            <div className="p-4 space-y-3">
              <Field label="类型">
                <select
                  value={p.type}
                  onChange={(e) => updProvider(name, { type: e.target.value })}
                  className={selectCls}
                >
                  <option value="openai">OpenAI</option>
                  <option value="anthropic">Anthropic</option>
                </select>
              </Field>
              <Field label="Base URL">
                <input
                  value={p.base_url}
                  onChange={(e) => updProvider(name, { base_url: e.target.value })}
                  className={inputCls}
                />
              </Field>
              <Field label="API Key">
                <input
                  type="password"
                  value={p.api_key}
                  onChange={(e) => updProvider(name, { api_key: e.target.value })}
                  className={inputCls}
                />
              </Field>
              <Field label="LLM 超时 (秒)">
                <input
                  type="number"
                  value={p.llm_timeout_secs}
                  onChange={(e) => updProvider(name, { llm_timeout_secs: +e.target.value })}
                  className={inputCls}
                />
              </Field>
              <Field label="记忆维护超时 (秒)">
                <input
                  type="number"
                  value={p.memory_maintenance_timeout_secs}
                  onChange={(e) =>
                    updProvider(name, { memory_maintenance_timeout_secs: +e.target.value })
                  }
                  className={inputCls}
                />
              </Field>
            </div>
          </div>
        ))}
        <button
          onClick={addProvider}
          className="flex items-center gap-2 px-4 py-2.5 rounded-xl border border-dashed border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--accent-cyan)] hover:border-[var(--accent-cyan)]/30 transition-colors text-sm w-full justify-center"
        >
          <Plus className="h-4 w-4" /> 添加服务商
        </button>
      </div>
    );
  };

  const renderModels = () => {
    const entries = Object.entries(config.models);
    const addModel = () => {
      const name = prompt('Model 名称:')?.trim();
      if (name && !config.models[name])
        update('models', { ...config.models, [name]: { model_id: name } });
    };
    const delModel = (name: string) => {
      if (confirm(`删除 Model "${name}"?`)) {
        const { [name]: _, ...rest } = config.models;
        update('models', rest);
      }
    };
    const updModel = (name: string, patch: Partial<ModelConfig>) =>
      update('models', { ...config.models, [name]: { ...config.models[name], ...patch } });
    return (
      <div className="space-y-4">
        {entries.map(([name, m]) => (
          <div
            key={name}
            className="rounded-xl border border-[var(--border-color)] bg-[var(--bg-secondary)]/60 overflow-hidden"
          >
            <MapEntryHeader name={name} onDelete={() => delModel(name)} />
            <div className="p-4 space-y-3">
              <Field label="Model ID">
                <input
                  value={m.model_id}
                  onChange={(e) => updModel(name, { model_id: e.target.value })}
                  className={inputCls}
                />
              </Field>
              <Field
                label="Temperature"
                hint="控制回复随机性，0 表示确定性输出，值越大越随机。留空使用模型默认值"
              >
                <input
                  type="number"
                  step="0.1"
                  value={m.temperature ?? ''}
                  onChange={(e) =>
                    updModel(name, { temperature: e.target.value ? +e.target.value : undefined })
                  }
                  className={inputCls}
                  placeholder="0.7"
                />
              </Field>
              <Field
                label="Max Tokens"
                hint="模型单次回复最大生成 token 数，超出会被截断。留空使用模型默认值（如 4096/8192）"
              >
                <input
                  type="number"
                  value={m.max_tokens ?? ''}
                  onChange={(e) =>
                    updModel(name, { max_tokens: e.target.value ? +e.target.value : undefined })
                  }
                  className={inputCls}
                  placeholder="4096"
                />
              </Field>
              <Field
                label="Context Window Tokens"
                hint="模型上下文窗口大小，用于内部历史消息压缩/裁剪计算。留空默认 128000"
              >
                <input
                  type="number"
                  value={m.context_window_tokens ?? ''}
                  onChange={(e) =>
                    updModel(name, {
                      context_window_tokens: e.target.value ? +e.target.value : undefined,
                    })
                  }
                  className={inputCls}
                  placeholder="128000"
                />
              </Field>
            </div>
          </div>
        ))}
        <button
          onClick={addModel}
          className="flex items-center gap-2 px-4 py-2.5 rounded-xl border border-dashed border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--accent-cyan)] hover:border-[var(--accent-cyan)]/30 transition-colors text-sm w-full justify-center"
        >
          <Plus className="h-4 w-4" /> 添加模型
        </button>
      </div>
    );
  };

  const renderAgents = () => {
    const entries = Object.entries(config.agents);
    const providerNames = Object.keys(config.providers);
    const modelNames = Object.keys(config.models);
    const addAgent = () => {
      const name = prompt('Agent 名称:')?.trim();
      if (name && !config.agents[name])
        update('agents', {
          ...config.agents,
          [name]: {
            provider: providerNames[0] || '',
            model: modelNames[0] || '',
            max_tool_iterations: 100,
            tool_result_max_chars: 100000,
            context_tool_result_trim_chars: 2000,
          },
        });
    };
    const delAgent = (name: string) => {
      if (confirm(`删除 Agent "${name}"?`)) {
        const { [name]: _, ...rest } = config.agents;
        update('agents', rest);
      }
    };
    const updAgent = (name: string, patch: Partial<AgentConfig>) =>
      update('agents', { ...config.agents, [name]: { ...config.agents[name], ...patch } });
    return (
      <div className="space-y-4">
        {entries.map(([name, a]) => (
          <div
            key={name}
            className="rounded-xl border border-[var(--border-color)] bg-[var(--bg-secondary)]/60 overflow-hidden"
          >
            <MapEntryHeader name={name} onDelete={() => delAgent(name)} />
            <div className="p-4 space-y-3">
              <Field label="Provider">
                <select
                  value={a.provider}
                  onChange={(e) => updAgent(name, { provider: e.target.value })}
                  className={selectCls}
                >
                  {providerNames.map((p) => (
                    <option key={p} value={p}>
                      {p}
                    </option>
                  ))}
                </select>
              </Field>
              <Field label="Model">
                <select
                  value={a.model}
                  onChange={(e) => updAgent(name, { model: e.target.value })}
                  className={selectCls}
                >
                  {modelNames.map((m) => (
                    <option key={m} value={m}>
                      {m}
                    </option>
                  ))}
                </select>
              </Field>
              <Field label="最大工具迭代次数">
                <input
                  type="number"
                  value={a.max_tool_iterations}
                  onChange={(e) => updAgent(name, { max_tool_iterations: +e.target.value })}
                  className={inputCls}
                />
              </Field>
              <Field label="工具结果最大字符数">
                <input
                  type="number"
                  value={a.tool_result_max_chars}
                  onChange={(e) => updAgent(name, { tool_result_max_chars: +e.target.value })}
                  className={inputCls}
                />
              </Field>
              <Field label="上下文工具结果裁剪字符数">
                <input
                  type="number"
                  value={a.context_tool_result_trim_chars}
                  onChange={(e) =>
                    updAgent(name, { context_tool_result_trim_chars: +e.target.value })
                  }
                  className={inputCls}
                />
              </Field>
            </div>
          </div>
        ))}
        <button
          onClick={addAgent}
          className="flex items-center gap-2 px-4 py-2.5 rounded-xl border border-dashed border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--accent-cyan)] hover:border-[var(--accent-cyan)]/30 transition-colors text-sm w-full justify-center"
        >
          <Plus className="h-4 w-4" /> 添加代理
        </button>
      </div>
    );
  };

  const renderTime = () => (
    <SectionCard title="时区设置">
      <Field label="时区" hint="IANA 格式">
        <select
          value={config.time.timezone}
          onChange={(e) => update('time', { timezone: e.target.value })}
          className={inputCls}
        >
          {TIMEZONE_OPTIONS.map((tz) => (
            <option key={tz.value} value={tz.value}>
              {tz.label}
            </option>
          ))}
        </select>
      </Field>
    </SectionCard>
  );

  const renderScheduler = () => (
    <div className="space-y-5">
      <SectionCard title="调度器">
        <div className="flex items-center justify-between">
          <span className="text-sm text-[var(--text-secondary)]">启用调度器</span>
          <Toggle
            checked={config.scheduler.enabled}
            onChange={(v) => update('scheduler', { ...config.scheduler, enabled: v })}
          />
        </div>
        <Field label="Tick 分辨率 (ms)">
          <input
            type="number"
            value={config.scheduler.tick_resolution_ms}
            onChange={(e) =>
              update('scheduler', { ...config.scheduler, tick_resolution_ms: +e.target.value })
            }
            className={inputCls}
          />
        </Field>
        <Field label="工作队列容量">
          <input
            type="number"
            value={config.scheduler.worker_queue_capacity}
            onChange={(e) =>
              update('scheduler', { ...config.scheduler, worker_queue_capacity: +e.target.value })
            }
            className={inputCls}
          />
        </Field>
        <Field label="Misfire 策略">
          <select
            value={config.scheduler.misfire_policy}
            onChange={(e) =>
              update('scheduler', {
                ...config.scheduler,
                misfire_policy: e.target.value as SchedulerConfig['misfire_policy'],
              })
            }
            className={selectCls}
          >
            <option value="skip">跳过 (Skip)</option>
            <option value="catch_up">追赶 (Catch Up)</option>
          </select>
        </Field>
      </SectionCard>
    </div>
  );

  const SKILL_KNOWN_SOURCES: KnownSource[] = [
    { key: 'user', label: '用户技能', description: '~/.picobot/skills' },
    { key: 'user_agent', label: '用户 Agent 技能', description: '~/.agents/skills' },
    { key: 'user_openclaw', label: '用户 OpenClaw 技能', description: '~/.openclaw/skills' },
    { key: 'project', label: '项目技能', description: '.picobot/skills' },
    { key: 'project_agent', label: '项目 Agent 技能', description: '.agents/skills' },
    { key: 'project_openclaw', label: '项目 OpenClaw 技能', description: '.openclaw/skills' },
  ];

  const SUBAGENT_KNOWN_SOURCES: KnownSource[] = [
    { key: 'user', label: '用户子代理', description: '~/.picobot/subagents' },
    { key: 'project', label: '项目子代理', description: '.picobot/subagents' },
  ];

  const renderSkills = () => (
    <div className="space-y-5">
      <SectionCard title="技能系统">
        <div className="flex items-center justify-between">
          <span className="text-sm text-[var(--text-secondary)]">启用技能</span>
          <Toggle
            checked={config.skills.enabled}
            onChange={(v) => update('skills', { ...config.skills, enabled: v })}
          />
        </div>
        <Field label="最大索引字符数">
          <input
            type="number"
            value={config.skills.max_index_chars}
            onChange={(e) =>
              update('skills', { ...config.skills, max_index_chars: +e.target.value })
            }
            className={inputCls}
          />
        </Field>
        <Field label="最大展示技能数">
          <input
            type="number"
            value={config.skills.max_listed_skills}
            onChange={(e) =>
              update('skills', { ...config.skills, max_listed_skills: +e.target.value })
            }
            className={inputCls}
          />
        </Field>
      </SectionCard>
      <SectionCard title="来源目录">
        <SourceEditor
          sources={config.skills.sources}
          onChange={(v) => update('skills', { ...config.skills, sources: v })}
          knownSources={SKILL_KNOWN_SOURCES}
          examplePaths={['D:\\my-skills', '/home/user/shared-skills']}
        />
      </SectionCard>
      {renderDiscoveredSkills()}
    </div>
  );

  const renderDiscoveredSkills = () => {
    if (!skillList || !skillList.skills_system_enabled) return null;

    const skills = skillList.skills;

    const handleToggle = async (name: string, currentlyEnabled: boolean) => {
      // Optimistic update
      const prevSkillList = skillList;
      setSkillList({
        ...skillList,
        skills: skills.map((s) =>
          s.name === name ? { ...s, disabled_in_scopes: currentlyEnabled ? ['project'] : [] } : s,
        ),
      });

      try {
        const resp = await toggleSkillCb(name, 'project', !currentlyEnabled);
        const data = await resp.json();
        if (!resp.ok || !data.success) {
          // Rollback
          setSkillList(prevSkillList);
          setToast(data.error || '切换技能状态失败');
          setTimeout(() => setToast(''), 3000);
          return;
        }
        // Update with server response
        setSkillList({
          ...prevSkillList,
          skills: prevSkillList.skills.map((s) =>
            s.name === name ? { ...s, disabled_in_scopes: data.disabled_in_scopes || [] } : s,
          ),
        });
      } catch {
        // Rollback
        setSkillList(prevSkillList);
        setToast('网络错误，切换技能状态失败');
        setTimeout(() => setToast(''), 3000);
      }
    };

    return (
      <SectionCard title="已发现技能" subtitle="即时生效">
        {skillListLoading && skills.length === 0 ? (
          <div className="flex items-center gap-2 text-sm text-[var(--text-muted)]">
            <Loader2 className="h-4 w-4 animate-spin" /> 加载中...
          </div>
        ) : skills.length === 0 ? (
          <p className="text-sm text-[var(--text-muted)]">未发现任何技能，请检查来源目录配置</p>
        ) : (
          <div className="space-y-1">
            {skills.map((skill) => {
              const isEnabled = skill.disabled_in_scopes.length === 0;
              return (
                <div
                  key={skill.name}
                  className="flex items-center gap-3 py-2 px-2 rounded-lg hover:bg-[var(--bg-hover)] transition-colors"
                >
                  <div className="flex-1 min-w-0">
                    <div className="flex items-center gap-2">
                      <span className="text-sm font-mono text-[var(--text-primary)]">
                        {skill.name}
                      </span>
                      <span className="text-[10px] px-1.5 py-0.5 rounded bg-[var(--bg-tertiary)] text-[var(--text-muted)] uppercase tracking-wider">
                        {skill.source}
                      </span>
                    </div>
                    <p className="text-xs text-[var(--text-muted)] truncate mt-0.5">
                      {skill.description}
                    </p>
                  </div>
                  <Toggle
                    checked={isEnabled}
                    onChange={() => handleToggle(skill.name, isEnabled)}
                  />
                </div>
              );
            })}
          </div>
        )}
      </SectionCard>
    );
  };

  const TASK_KNOWN_TOOLS: KnownSource[] = [
    { key: 'read', label: 'Read', description: '读取文件' },
    { key: 'edit', label: 'Edit', description: '编辑文件' },
    { key: 'write', label: 'Write', description: '写入文件' },
    { key: 'bash', label: 'Bash', description: '执行 Shell 命令' },
    { key: 'http_request', label: 'HTTP Request', description: '发送 HTTP 请求' },
    { key: 'web_fetch', label: 'Web Fetch', description: '抓取网页内容' },
    { key: 'memory_search', label: 'Memory Search', description: '搜索记忆' },
    { key: 'get_time', label: 'Get Time', description: '获取当前时间' },
    { key: 'calculator', label: 'Calculator', description: '计算器' },
    { key: 'skill_activate', label: 'Skill Activate', description: '激活技能' },
    { key: 'skill_list', label: 'Skill List', description: '列出技能' },
    { key: 'send_session_message', label: 'Send Session Message', description: '发送会话消息' },
  ];

  const renderTools = () => (
    <div className="space-y-5">
      <SectionCard title="禁用工具列表">
        <TagEditor
          tags={config.tools.disabled}
          onChange={(v) => update('tools', { ...config.tools, disabled: v })}
        />
      </SectionCard>
      <SectionCard title="Task 子代理">
        <div className="flex items-center justify-between">
          <span className="text-sm text-[var(--text-secondary)]">启用 Task 工具</span>
          <Toggle
            checked={config.tools.task.enabled}
            onChange={(v) =>
              update('tools', { ...config.tools, task: { ...config.tools.task, enabled: v } })
            }
          />
        </div>
        <Field label="最大执行时间 (秒)">
          <input
            type="number"
            value={config.tools.task.max_execution_secs}
            onChange={(e) =>
              update('tools', {
                ...config.tools,
                task: { ...config.tools.task, max_execution_secs: +e.target.value },
              })
            }
            className={inputCls}
          />
        </Field>
        <Field label="TTL (小时)">
          <input
            type="number"
            value={config.tools.task.ttl_hours}
            onChange={(e) =>
              update('tools', {
                ...config.tools,
                task: { ...config.tools.task, ttl_hours: +e.target.value },
              })
            }
            className={inputCls}
          />
        </Field>
        <Field
          label="最大嵌套深度"
          hint="允许的子代理最大嵌套层数。1=仅子代理，2=子代理+孙代理（默认），0=禁止嵌套。深度达上限时移除 task 工具以防无限递归"
        >
          <input
            type="number"
            min={0}
            value={config.tools.task.max_nesting_depth}
            onChange={(e) =>
              update('tools', {
                ...config.tools,
                task: {
                  ...config.tools.task,
                  max_nesting_depth: Math.max(0, +e.target.value || 0),
                },
              })
            }
            className={inputCls}
          />
        </Field>
      </SectionCard>
      <SectionCard title="允许的工具列表">
        <SourceEditor
          sources={config.tools.task.allowed_tools}
          onChange={(v) =>
            update('tools', { ...config.tools, task: { ...config.tools.task, allowed_tools: v } })
          }
          knownSources={TASK_KNOWN_TOOLS}
          showCustom={false}
        />
      </SectionCard>
    </div>
  );

  const renderMemory = () => (
    <SectionCard title="记忆维护">
      <Field label="最大合并比例" hint="0.0 - 1.0，单次最多合并/删除的记忆比例">
        <input
          type="number"
          step="0.05"
          min="0"
          max="1"
          value={config.memory_maintenance.max_merge_ratio}
          onChange={(e) =>
            update('memory_maintenance', {
              ...config.memory_maintenance,
              max_merge_ratio: +e.target.value,
            })
          }
          className={inputCls}
        />
      </Field>
      <Field label="最小保留记忆数">
        <input
          type="number"
          value={config.memory_maintenance.min_memories_to_keep}
          onChange={(e) =>
            update('memory_maintenance', {
              ...config.memory_maintenance,
              min_memories_to_keep: +e.target.value,
            })
          }
          className={inputCls}
        />
      </Field>
      <Field label="单组最大合并数">
        <input
          type="number"
          value={config.memory_maintenance.max_merge_per_group}
          onChange={(e) =>
            update('memory_maintenance', {
              ...config.memory_maintenance,
              max_merge_per_group: +e.target.value,
            })
          }
          className={inputCls}
        />
      </Field>
    </SectionCard>
  );

  const renderImage = () => (
    <SectionCard title="图片上下文">
      <Field label="上下文中最大图片数" hint="发送给模型的图片数量上限">
        <input
          type="number"
          value={config.image_context.max_images_in_context}
          onChange={(e) =>
            update('image_context', {
              ...config.image_context,
              max_images_in_context: +e.target.value,
            })
          }
          className={inputCls}
        />
      </Field>
      <Field label="图片最大存活轮次" hint="超过此轮次后不再提交给模型">
        <input
          type="number"
          value={config.image_context.max_image_age_rounds}
          onChange={(e) =>
            update('image_context', {
              ...config.image_context,
              max_image_age_rounds: +e.target.value,
            })
          }
          className={inputCls}
        />
      </Field>
    </SectionCard>
  );

  const renderSubagents = () => (
    <div className="space-y-5">
      <SectionCard title="子代理">
        <div className="flex items-center justify-between">
          <span className="text-sm text-[var(--text-secondary)]">启用子代理发现</span>
          <Toggle
            checked={config.subagents.enabled}
            onChange={(v) => update('subagents', { ...config.subagents, enabled: v })}
          />
        </div>
      </SectionCard>
      <SectionCard title="来源目录">
        <SourceEditor
          sources={config.subagents.sources}
          onChange={(v) => update('subagents', { ...config.subagents, sources: v })}
          knownSources={SUBAGENT_KNOWN_SOURCES}
          examplePaths={['D:\\my-subagents', '/home/user/shared-agents']}
        />
      </SectionCard>
      {renderDiscoveredSubagents()}
      {renderSubagentModal()}
    </div>
  );

  const renderDiscoveredSubagents = () => {
    if (!subagentList || !subagentList.subagents_system_enabled) return null;

    const subagents = subagentList.subagents;

    const handleToggle = async (name: string, currentlyEnabled: boolean) => {
      const prevList = subagentList;
      setSubagentList({
        ...subagentList,
        subagents: subagents.map((s) =>
          s.name === name ? { ...s, disabled_in_scopes: currentlyEnabled ? ['project'] : [] } : s,
        ),
      });

      try {
        const resp = await toggleSubagentCb(name, 'project', !currentlyEnabled);
        const data = await resp.json();
        if (!resp.ok || !data.success) {
          setSubagentList(prevList);
          setToast(data.error || '切换子代理状态失败');
          setTimeout(() => setToast(''), 3000);
          return;
        }
        setSubagentList({
          ...prevList,
          subagents: prevList.subagents.map((s) =>
            s.name === name ? { ...s, disabled_in_scopes: data.disabled_in_scopes || [] } : s,
          ),
        });
      } catch {
        setSubagentList(prevList);
        setToast('网络错误，切换子代理状态失败');
        setTimeout(() => setToast(''), 3000);
      }
    };

    const toolLabel = (key: string): string => {
      const known = TASK_KNOWN_TOOLS.find((t) => t.key === key);
      return known ? known.label : key;
    };

    const renderToolTags = (label: string, tools: string[] | undefined, tone: 'allow' | 'deny') => {
      if (!tools || tools.length === 0) return null;
      const tagCls =
        tone === 'allow'
          ? 'bg-emerald-500/10 text-emerald-600 dark:text-emerald-400'
          : 'bg-rose-500/10 text-rose-600 dark:text-rose-400';
      return (
        <div className="flex items-center gap-1 flex-wrap mt-1">
          <span className="text-[10px] text-[var(--text-muted)]">{label}:</span>
          {tools.map((t) => (
            <span key={t} className={`text-[10px] px-1.5 py-0.5 rounded ${tagCls}`}>
              {toolLabel(t)}
            </span>
          ))}
        </div>
      );
    };

    const handleEditSubagent = (subagent: SubagentItem) => {
      setEditingSubagentError('');
      setEditingSubagent({
        name: subagent.name,
        description: subagent.description,
        provider: subagent.provider ?? '',
        model: subagent.model ?? '',
        allowedSkills: subagent.capability?.allowed_skills ?? [],
        deniedSkills: subagent.capability?.denied_skills ?? [],
        allowedTools: subagent.capability?.allowed_tools ?? [],
        deniedTools: subagent.capability?.denied_tools ?? [],
        allowedSubagents: subagent.capability?.allowed_subagents ?? [],
        deniedSubagents: subagent.capability?.denied_subagents ?? [],
      });
    };

    return (
      <SectionCard title="已发现子代理" subtitle="即时生效">
        {subagentListLoading && subagents.length === 0 ? (
          <div className="flex items-center gap-2 text-sm text-[var(--text-muted)]">
            <Loader2 className="h-4 w-4 animate-spin" /> 加载中...
          </div>
        ) : subagents.length === 0 ? (
          <p className="text-sm text-[var(--text-muted)]">未发现任何子代理</p>
        ) : (
          <div className="space-y-1">
            {subagents.map((subagent) => {
              const isEnabled = subagent.disabled_in_scopes.length === 0;
              const isBuiltin = subagent.source === 'builtin';
              return (
                <div
                  key={subagent.name}
                  className="flex items-center gap-3 py-2 px-2 rounded-lg hover:bg-[var(--bg-hover)] transition-colors"
                >
                  <div className="flex-1 min-w-0">
                    <div className="flex items-center gap-2">
                      <span className="text-sm font-mono text-[var(--text-primary)]">
                        {subagent.name}
                      </span>
                      <span className="text-[10px] px-1.5 py-0.5 rounded bg-[var(--bg-tertiary)] text-[var(--text-muted)] uppercase tracking-wider">
                        {subagent.source}
                      </span>
                    </div>
                    <p className="text-xs text-[var(--text-muted)] truncate mt-0.5">
                      {subagent.description}
                    </p>
                    {renderToolTags('允许', subagent.capability?.allowed_tools, 'allow')}
                    {renderToolTags('禁用', subagent.capability?.denied_tools, 'deny')}
                  </div>
                  {!isBuiltin && (
                    <button
                      onClick={() => handleEditSubagent(subagent)}
                      className="p-1 rounded text-[var(--text-muted)] hover:text-[var(--accent-cyan)] hover:bg-[var(--overlay-hover)] transition-colors"
                      title="编辑"
                    >
                      <Pencil className="h-3.5 w-3.5" />
                    </button>
                  )}
                  <Toggle
                    checked={isEnabled}
                    onChange={() => handleToggle(subagent.name, isEnabled)}
                  />
                </div>
              );
            })}
          </div>
        )}
      </SectionCard>
    );
  };

  const EXPERT_KNOWN_SOURCES: KnownSource[] = [
    { key: 'user', label: '用户专家', description: '~/.picobot/experts' },
    { key: 'project', label: '项目专家', description: '.picobot/experts' },
  ];

  const renderExperts = () => (
    <div className="space-y-5">
      <SectionCard title="专家系统">
        <div className="flex items-center justify-between">
          <span className="text-sm text-[var(--text-secondary)]">启用专家系统</span>
          <Toggle
            checked={config.experts.enabled}
            onChange={(v) => update('experts', { ...config.experts, enabled: v })}
          />
        </div>
      </SectionCard>
      <SectionCard title="来源目录">
        <SourceEditor
          sources={config.experts.sources}
          onChange={(v) => update('experts', { ...config.experts, sources: v })}
          knownSources={EXPERT_KNOWN_SOURCES}
          examplePaths={['D:\\my-experts', '/home/user/shared-experts']}
        />
      </SectionCard>
      {renderDiscoveredExperts()}
      <button
        onClick={() => {
          setEditingExpertError('');
          setEditingExpert({
            mode: 'create',
            scope: 'project',
            nameField: '',
            description: '',
            body: '',
            provider: '',
            model: '',
            allowedSkills: [],
            deniedSkills: [],
            allowedTools: [],
            deniedTools: [],
            allowedSubagents: [],
            deniedSubagents: [],
          });
        }}
        className="flex items-center gap-2 px-4 py-2.5 rounded-xl border border-dashed border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--accent-cyan)] hover:border-[var(--accent-cyan)]/30 transition-colors text-sm w-full justify-center"
      >
        <Plus className="h-4 w-4" /> 添加专家
      </button>
      {renderExpertModal()}
    </div>
  );

  const renderDiscoveredExperts = () => {
    if (!expertList || !expertList.experts_system_enabled) return null;

    const experts = expertList.experts;

    const handleToggle = async (name: string, currentlyEnabled: boolean) => {
      const prevList = expertList;
      setExpertList({
        ...expertList,
        experts: experts.map((e) =>
          e.name === name ? { ...e, disabled_in_scopes: currentlyEnabled ? ['project'] : [] } : e,
        ),
      });

      try {
        const resp = await toggleExpertCb(name, 'project', !currentlyEnabled);
        const data = await resp.json();
        if (!resp.ok || !data.success) {
          setExpertList(prevList);
          setToast(data.error || '切换专家状态失败');
          setTimeout(() => setToast(''), 3000);
          return;
        }
        setExpertList({
          ...prevList,
          experts: prevList.experts.map((e) =>
            e.name === name ? { ...e, disabled_in_scopes: data.disabled_in_scopes || [] } : e,
          ),
        });
      } catch {
        setExpertList(prevList);
        setToast('网络错误，切换专家状态失败');
        setTimeout(() => setToast(''), 3000);
      }
    };

    const handleEdit = (expert: ExpertItem) => {
      setEditingExpertError('');
      setEditingExpert({
        mode: 'edit',
        name: expert.name,
        scope: 'project',
        nameField: expert.name,
        description: expert.description,
        body: expert.body ?? '',
        provider: expert.provider ?? '',
        model: expert.model ?? '',
        allowedSkills: expert.capability?.allowed_skills ?? [],
        deniedSkills: expert.capability?.denied_skills ?? [],
        allowedTools: expert.capability?.allowed_tools ?? [],
        deniedTools: expert.capability?.denied_tools ?? [],
        allowedSubagents: expert.capability?.allowed_subagents ?? [],
        deniedSubagents: expert.capability?.denied_subagents ?? [],
      });
    };

    const handleDelete = async (name: string) => {
      if (!confirm(`确定删除专家 "${name}" 吗？此操作将删除对应文件。`)) return;
      try {
        const resp = await deleteExpertCb(name, 'project');
        const data = await resp.json();
        if (!resp.ok || !data.success) {
          setToast(data.error || '删除专家失败');
          setTimeout(() => setToast(''), 3000);
          return;
        }
        setToast('专家已删除');
        setTimeout(() => setToast(''), 3000);
        fetchExpertList();
      } catch {
        setToast('网络错误，删除专家失败');
        setTimeout(() => setToast(''), 3000);
      }
    };

    return (
      <SectionCard title="已发现专家" subtitle="即时生效">
        {expertListLoading && experts.length === 0 ? (
          <div className="flex items-center gap-2 text-sm text-[var(--text-muted)]">
            <Loader2 className="h-4 w-4 animate-spin" /> 加载中...
          </div>
        ) : experts.length === 0 ? (
          <p className="text-sm text-[var(--text-muted)]">未发现任何专家</p>
        ) : (
          <div className="space-y-1">
            {experts.map((expert) => {
              const isEnabled = expert.disabled_in_scopes.length === 0;
              return (
                <div
                  key={expert.name}
                  className="flex items-center gap-3 py-2 px-2 rounded-lg hover:bg-[var(--bg-hover)] transition-colors"
                >
                  <div className="flex-1 min-w-0">
                    <div className="flex items-center gap-2">
                      <span className="text-sm font-mono text-[var(--text-primary)]">
                        {expert.name}
                      </span>
                      <span className="text-[10px] px-1.5 py-0.5 rounded bg-[var(--bg-tertiary)] text-[var(--text-muted)] uppercase tracking-wider">
                        {expert.source}
                      </span>
                    </div>
                    <p className="text-xs text-[var(--text-muted)] truncate mt-0.5">
                      {expert.description}
                    </p>
                  </div>
                  <button
                    onClick={() => handleEdit(expert)}
                    className="p-1 rounded text-[var(--text-muted)] hover:text-[var(--accent-cyan)] hover:bg-[var(--overlay-hover)] transition-colors"
                    title="编辑"
                  >
                    <Pencil className="h-3.5 w-3.5" />
                  </button>
                  <button
                    onClick={() => handleDelete(expert.name)}
                    className="p-1 rounded text-[var(--text-muted)] hover:text-red-400 hover:bg-red-500/10 transition-colors"
                    title="删除"
                  >
                    <Trash2 className="h-3.5 w-3.5" />
                  </button>
                  <Toggle
                    checked={isEnabled}
                    onChange={() => handleToggle(expert.name, isEnabled)}
                  />
                </div>
              );
            })}
          </div>
        )}
      </SectionCard>
    );
  };

  // 技能/工具勾选选项（专家与子代理编辑模态框共用）
  const skillOptions = (skillList?.skills ?? []).map((s) => ({
    key: s.name,
    label: s.name,
    description: s.description,
    group: s.source,
  }));
  const toolOptions = (toolList?.tools ?? []).map((t) => ({
    key: t.name,
    label: t.name,
    description: t.description,
    group: t.source,
  }));
  const subagentOptions = (subagentList?.subagents ?? []).map((s) => ({
    key: s.name,
    label: s.name,
    description: s.description,
    group: s.source,
  }));
  const skillEmptyHint = skillListLoading
    ? '加载中...'
    : '未发现任何技能，请先在技能页配置来源目录';
  const toolEmptyHint = toolListLoading ? '加载中...' : '未发现任何工具';
  const subagentEmptyHint = subagentListLoading ? '加载中...' : '未发现任何子代理';

  const renderExpertModal = () => {
    if (!editingExpert) return null;
    const isEdit = editingExpert.mode === 'edit';
    const canSave = editingExpert.nameField.trim() && editingExpert.description.trim();

    const handleSave = async () => {
      if (!canSave) return;
      setSavingExpert(true);
      setEditingExpertError('');
      try {
        // allowed_* 为空时必须传 undefined（后端 None=不限），
        // 否则空数组会被反序列化为 Some(vec![]) 触发白名单空集语义（全禁）。
        // denied_* 为 Vec<String>，空数组即"不禁"，可直接传。
        const capability: CapabilityPolicy = {
          allowed_skills:
            editingExpert.allowedSkills.length > 0 ? editingExpert.allowedSkills : undefined,
          denied_skills: editingExpert.deniedSkills,
          allowed_tools:
            editingExpert.allowedTools.length > 0 ? editingExpert.allowedTools : undefined,
          denied_tools: editingExpert.deniedTools,
          allowed_subagents:
            editingExpert.allowedSubagents.length > 0 ? editingExpert.allowedSubagents : undefined,
          denied_subagents: editingExpert.deniedSubagents,
        };
        const payload = {
          name: editingExpert.nameField,
          description: editingExpert.description,
          body: editingExpert.body,
          capability,
          provider: editingExpert.provider || undefined,
          model: editingExpert.model || undefined,
        };
        const resp = isEdit
          ? await updateExpertCb({ ...payload, scope: 'project' })
          : await createExpertCb({ ...payload, scope: 'project' });
        const data = await resp.json().catch(() => ({}));
        if (!resp.ok) {
          setEditingExpertError(data.error || data.message || '保存失败');
          setSavingExpert(false);
          return;
        }
        setToast(isEdit ? '专家已更新' : '专家已创建');
        setTimeout(() => setToast(''), 3000);
        setEditingExpert(null);
        fetchExpertList();
      } catch (e: unknown) {
        setEditingExpertError(e instanceof Error ? e.message : '网络错误');
      } finally {
        setSavingExpert(false);
      }
    };

    return (
      <div
        className="absolute inset-0 z-20 flex items-center justify-center bg-black/50 backdrop-blur-sm rounded-2xl"
        onClick={() => setEditingExpert(null)}
      >
        <div
          className="bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-xl w-[90%] max-w-3xl mx-4 shadow-2xl animate-[scaleIn_0.2s_ease-out] max-h-[90%] flex flex-col overflow-hidden"
          onClick={(e) => e.stopPropagation()}
        >
          <ModalHeader
            icon={<UserCheck className="h-5 w-5 text-[var(--accent-cyan)]" />}
            title={isEdit ? '编辑专家' : '添加专家'}
            onClose={() => setEditingExpert(null)}
          />
          <div className="flex-1 overflow-y-auto p-6 space-y-4">
            <SectionCard title="基本信息">
              <Field
                label="名称"
                hint="创建后不可修改。仅当正文为空时，与描述一起生成兜底提示词；正文非空时不注入"
              >
                <input
                  value={editingExpert.nameField}
                  onChange={(e) =>
                    setEditingExpert((prev) =>
                      prev ? { ...prev, nameField: e.target.value } : prev,
                    )
                  }
                  disabled={isEdit}
                  placeholder="如 translator"
                  className={inputCls + (isEdit ? ' opacity-60 cursor-not-allowed' : '')}
                  autoFocus={!isEdit}
                />
              </Field>
              <Field
                label="描述"
                hint="必填。仅当正文为空时，与名称一起生成兜底提示词；正文非空时不注入"
              >
                <input
                  value={editingExpert.description}
                  onChange={(e) =>
                    setEditingExpert((prev) =>
                      prev ? { ...prev, description: e.target.value } : prev,
                    )
                  }
                  placeholder="如 翻译专家"
                  className={inputCls}
                />
              </Field>
              <Field
                label="专家提示词正文"
                hint="markdown 格式。非空时仅注入正文；为空时自动用“名称+描述”生成兜底提示词"
              >
                <textarea
                  value={editingExpert.body}
                  onChange={(e) =>
                    setEditingExpert((prev) => (prev ? { ...prev, body: e.target.value } : prev))
                  }
                  placeholder="你是一名专业翻译..."
                  className={inputCls + ' min-h-[160px] resize-y font-mono text-xs'}
                />
              </Field>
            </SectionCard>
            <SectionCard title="模型配置" subtitle="留空继承默认">
              <Field label="Provider" hint="留空继承默认配置">
                <select
                  value={editingExpert.provider}
                  onChange={(e) =>
                    setEditingExpert((prev) =>
                      prev ? { ...prev, provider: e.target.value } : prev,
                    )
                  }
                  className={selectCls}
                >
                  <option value="">继承默认</option>
                  {(modelOptions?.providers ?? []).map((p) => (
                    <option key={p} value={p}>
                      {p}
                    </option>
                  ))}
                </select>
              </Field>
              <Field label="Model" hint="留空继承默认配置">
                <select
                  value={editingExpert.model}
                  onChange={(e) =>
                    setEditingExpert((prev) => (prev ? { ...prev, model: e.target.value } : prev))
                  }
                  className={selectCls}
                >
                  <option value="">继承默认</option>
                  {(modelOptions?.models ?? []).map((m) => (
                    <option key={m} value={m}>
                      {m}
                    </option>
                  ))}
                </select>
              </Field>
            </SectionCard>
            <SectionCard title="技能能力" subtitle="白名单取交集，黑名单扣除">
              <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
                <Field label="允许的技能（白名单）" hint="留空表示不限">
                  <CheckboxList
                    options={skillOptions}
                    selected={editingExpert.allowedSkills}
                    onChange={(v) =>
                      setEditingExpert((prev) => (prev ? { ...prev, allowedSkills: v } : prev))
                    }
                    extraSelected={editingExpert.allowedSkills}
                    emptyHint={skillEmptyHint}
                    groupBy={(o) => o.group ?? '其他'}
                  />
                </Field>
                <Field label="禁用的技能（黑名单）" hint="在白名单之后应用">
                  <CheckboxList
                    options={skillOptions}
                    selected={editingExpert.deniedSkills}
                    onChange={(v) =>
                      setEditingExpert((prev) => (prev ? { ...prev, deniedSkills: v } : prev))
                    }
                    extraSelected={editingExpert.deniedSkills}
                    emptyHint={skillEmptyHint}
                    groupBy={(o) => o.group ?? '其他'}
                  />
                </Field>
              </div>
            </SectionCard>
            <SectionCard title="工具能力" subtitle="含 mcp_*">
              <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
                <Field label="允许的工具（白名单）" hint="留空表示不限">
                  <CheckboxList
                    options={toolOptions}
                    selected={editingExpert.allowedTools}
                    onChange={(v) =>
                      setEditingExpert((prev) => (prev ? { ...prev, allowedTools: v } : prev))
                    }
                    extraSelected={editingExpert.allowedTools}
                    emptyHint={toolEmptyHint}
                    groupBy={(o) => o.group ?? '其他'}
                  />
                </Field>
                <Field label="禁用的工具" hint="在白名单之后应用">
                  <CheckboxList
                    options={toolOptions}
                    selected={editingExpert.deniedTools}
                    onChange={(v) =>
                      setEditingExpert((prev) => (prev ? { ...prev, deniedTools: v } : prev))
                    }
                    extraSelected={editingExpert.deniedTools}
                    emptyHint={toolEmptyHint}
                    groupBy={(o) => o.group ?? '其他'}
                  />
                </Field>
              </div>
            </SectionCard>
            <SectionCard title="子代理能力" subtitle="限制可加载的子代理">
              <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
                <Field label="允许的子代理（白名单）" hint="留空表示不限。仅勾选的子代理可被加载">
                  <CheckboxList
                    options={subagentOptions}
                    selected={editingExpert.allowedSubagents}
                    onChange={(v) =>
                      setEditingExpert((prev) => (prev ? { ...prev, allowedSubagents: v } : prev))
                    }
                    extraSelected={editingExpert.allowedSubagents}
                    emptyHint={subagentEmptyHint}
                    groupBy={(o) => o.group ?? '其他'}
                  />
                </Field>
                <Field label="禁用的子代理（黑名单）" hint="在白名单之后应用">
                  <CheckboxList
                    options={subagentOptions}
                    selected={editingExpert.deniedSubagents}
                    onChange={(v) =>
                      setEditingExpert((prev) => (prev ? { ...prev, deniedSubagents: v } : prev))
                    }
                    extraSelected={editingExpert.deniedSubagents}
                    emptyHint={subagentEmptyHint}
                    groupBy={(o) => o.group ?? '其他'}
                  />
                </Field>
              </div>
            </SectionCard>
            {editingExpertError && (
              <div className="text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-lg px-3 py-2">
                {editingExpertError}
              </div>
            )}
          </div>
          <ModalFooter>
            <button
              onClick={() => setEditingExpert(null)}
              className="px-4 py-2 rounded-lg text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--overlay-hover)] transition-colors"
            >
              取消
            </button>
            <button
              onClick={handleSave}
              disabled={!canSave || savingExpert}
              className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium text-white bg-[var(--accent-cyan)]/20 border border-[var(--accent-cyan)]/30 hover:bg-[var(--accent-cyan)]/30 hover:border-[var(--accent-cyan)]/50 transition-all disabled:opacity-40 disabled:cursor-not-allowed"
            >
              {savingExpert ? (
                <Loader2 className="h-4 w-4 animate-spin" />
              ) : (
                <Save className="h-4 w-4" />
              )}
              {savingExpert ? '保存中...' : '保存'}
            </button>
          </ModalFooter>
        </div>
      </div>
    );
  };

  const renderSubagentModal = () => {
    if (!editingSubagent) return null;
    const canSave = editingSubagent.description.trim().length > 0;
    // 排除自身，避免子代理勾选自己造成意外自递归（max_nesting_depth 仍兜底）
    const subagentOptionsExcludingSelf = subagentOptions.filter(
      (o) => o.key !== editingSubagent.name,
    );

    const handleSave = async () => {
      if (!canSave) return;
      setSavingSubagent(true);
      setEditingSubagentError('');
      try {
        // 与专家一致：allowed_* 为空时传 undefined（None=不限），denied_* 空数组即"不禁"
        // 必须包含全部 6 个字段，否则后端 #[serde(default)] 会让缺失字段变为 None/vec![]，
        // 经 next_capability 完全覆盖原 capability，导致既有策略被清空（数据丢失）
        const capability: CapabilityPolicy = {
          allowed_skills:
            editingSubagent.allowedSkills.length > 0 ? editingSubagent.allowedSkills : undefined,
          denied_skills: editingSubagent.deniedSkills,
          allowed_tools:
            editingSubagent.allowedTools.length > 0 ? editingSubagent.allowedTools : undefined,
          denied_tools: editingSubagent.deniedTools,
          allowed_subagents:
            editingSubagent.allowedSubagents.length > 0
              ? editingSubagent.allowedSubagents
              : undefined,
          denied_subagents: editingSubagent.deniedSubagents,
        };
        const resp = await updateSubagentCb({
          name: editingSubagent.name,
          description: editingSubagent.description,
          capability,
          provider: editingSubagent.provider || undefined,
          model: editingSubagent.model || undefined,
        });
        const data = await resp.json().catch(() => ({}));
        if (!resp.ok) {
          setEditingSubagentError(data.error || data.message || '保存失败');
          setSavingSubagent(false);
          return;
        }
        setToast('子代理已更新');
        setTimeout(() => setToast(''), 3000);
        setEditingSubagent(null);
        fetchSubagentList();
      } catch (e: unknown) {
        setEditingSubagentError(e instanceof Error ? e.message : '网络错误');
      } finally {
        setSavingSubagent(false);
      }
    };

    return (
      <div
        className="absolute inset-0 z-20 flex items-center justify-center bg-black/50 backdrop-blur-sm rounded-2xl"
        onClick={() => setEditingSubagent(null)}
      >
        <div
          className="bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-xl w-[90%] max-w-3xl mx-4 shadow-2xl animate-[scaleIn_0.2s_ease-out] max-h-[90%] flex flex-col overflow-hidden"
          onClick={(e) => e.stopPropagation()}
        >
          <ModalHeader
            icon={<Bot className="h-5 w-5 text-[var(--accent-cyan)]" />}
            title="编辑子代理"
            onClose={() => setEditingSubagent(null)}
          />
          <div className="flex-1 overflow-y-auto p-6 space-y-4">
            <SectionCard title="基本信息">
              <Field label="名称">
                <input
                  value={editingSubagent.name}
                  disabled
                  className={inputCls + ' opacity-60 cursor-not-allowed'}
                />
              </Field>
              <Field label="描述" hint="子代理的简短描述，用于主智能体选择">
                <input
                  value={editingSubagent.description}
                  onChange={(e) =>
                    setEditingSubagent((prev) =>
                      prev ? { ...prev, description: e.target.value } : prev,
                    )
                  }
                  className={inputCls}
                />
              </Field>
            </SectionCard>
            <SectionCard title="模型配置" subtitle="留空继承默认">
              <Field label="Provider" hint="留空继承默认配置">
                <select
                  value={editingSubagent.provider}
                  onChange={(e) =>
                    setEditingSubagent((prev) =>
                      prev ? { ...prev, provider: e.target.value } : prev,
                    )
                  }
                  className={selectCls}
                >
                  <option value="">继承默认</option>
                  {(modelOptions?.providers ?? []).map((p) => (
                    <option key={p} value={p}>
                      {p}
                    </option>
                  ))}
                </select>
              </Field>
              <Field label="Model" hint="留空继承默认配置">
                <select
                  value={editingSubagent.model}
                  onChange={(e) =>
                    setEditingSubagent((prev) => (prev ? { ...prev, model: e.target.value } : prev))
                  }
                  className={selectCls}
                >
                  <option value="">继承默认</option>
                  {(modelOptions?.models ?? []).map((m) => (
                    <option key={m} value={m}>
                      {m}
                    </option>
                  ))}
                </select>
              </Field>
            </SectionCard>
            <SectionCard title="技能能力" subtitle="白名单取交集，黑名单扣除">
              <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
                <Field label="允许的技能（白名单）" hint="留空表示不限。仅勾选的 SKILL.md 技能可见">
                  <CheckboxList
                    options={skillOptions}
                    selected={editingSubagent.allowedSkills}
                    onChange={(v) =>
                      setEditingSubagent((prev) => (prev ? { ...prev, allowedSkills: v } : prev))
                    }
                    extraSelected={editingSubagent.allowedSkills}
                    emptyHint={skillEmptyHint}
                    groupBy={(o) => o.group ?? '其他'}
                  />
                </Field>
                <Field label="禁用的技能（黑名单）" hint="在白名单之后应用">
                  <CheckboxList
                    options={skillOptions}
                    selected={editingSubagent.deniedSkills}
                    onChange={(v) =>
                      setEditingSubagent((prev) => (prev ? { ...prev, deniedSkills: v } : prev))
                    }
                    extraSelected={editingSubagent.deniedSkills}
                    emptyHint={skillEmptyHint}
                    groupBy={(o) => o.group ?? '其他'}
                  />
                </Field>
              </div>
            </SectionCard>
            <SectionCard title="工具能力" subtitle="含 mcp_*">
              <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
                <Field label="允许的工具（白名单）" hint="留空表示不限。覆盖内置 + MCP 工具">
                  <CheckboxList
                    options={toolOptions}
                    selected={editingSubagent.allowedTools}
                    onChange={(v) =>
                      setEditingSubagent((prev) => (prev ? { ...prev, allowedTools: v } : prev))
                    }
                    extraSelected={editingSubagent.allowedTools}
                    emptyHint={toolEmptyHint}
                    groupBy={(o) => o.group ?? '其他'}
                  />
                </Field>
                <Field label="禁用的工具" hint="在白名单之后应用">
                  <CheckboxList
                    options={toolOptions}
                    selected={editingSubagent.deniedTools}
                    onChange={(v) =>
                      setEditingSubagent((prev) => (prev ? { ...prev, deniedTools: v } : prev))
                    }
                    extraSelected={editingSubagent.deniedTools}
                    emptyHint={toolEmptyHint}
                    groupBy={(o) => o.group ?? '其他'}
                  />
                </Field>
              </div>
            </SectionCard>
            <SectionCard title="子代理能力" subtitle="限制可加载的孙代理">
              <div className="grid grid-cols-1 md:grid-cols-2 gap-3">
                <Field
                  label="允许的子代理（白名单）"
                  hint="留空表示不限。仅勾选的子代理可被加载为孙代理"
                >
                  <CheckboxList
                    options={subagentOptionsExcludingSelf}
                    selected={editingSubagent.allowedSubagents}
                    onChange={(v) =>
                      setEditingSubagent((prev) => (prev ? { ...prev, allowedSubagents: v } : prev))
                    }
                    extraSelected={editingSubagent.allowedSubagents}
                    emptyHint={subagentEmptyHint}
                    groupBy={(o) => o.group ?? '其他'}
                  />
                </Field>
                <Field label="禁用的子代理（黑名单）" hint="在白名单之后应用">
                  <CheckboxList
                    options={subagentOptionsExcludingSelf}
                    selected={editingSubagent.deniedSubagents}
                    onChange={(v) =>
                      setEditingSubagent((prev) => (prev ? { ...prev, deniedSubagents: v } : prev))
                    }
                    extraSelected={editingSubagent.deniedSubagents}
                    emptyHint={subagentEmptyHint}
                    groupBy={(o) => o.group ?? '其他'}
                  />
                </Field>
              </div>
            </SectionCard>
            {editingSubagentError && (
              <div className="text-sm text-red-400 bg-red-500/10 border border-red-500/20 rounded-lg px-3 py-2">
                {editingSubagentError}
              </div>
            )}
          </div>
          <ModalFooter>
            <button
              onClick={() => setEditingSubagent(null)}
              className="px-4 py-2 rounded-lg text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--overlay-hover)] transition-colors"
            >
              取消
            </button>
            <button
              onClick={handleSave}
              disabled={!canSave || savingSubagent}
              className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium text-white bg-[var(--accent-cyan)]/20 border border-[var(--accent-cyan)]/30 hover:bg-[var(--accent-cyan)]/30 hover:border-[var(--accent-cyan)]/50 transition-all disabled:opacity-40 disabled:cursor-not-allowed"
            >
              {savingSubagent ? (
                <Loader2 className="h-4 w-4 animate-spin" />
              ) : (
                <Save className="h-4 w-4" />
              )}
              {savingSubagent ? '保存中...' : '保存'}
            </button>
          </ModalFooter>
        </div>
      </div>
    );
  };

  const renderMcp = () => {
    const entries = Object.entries(config.mcpServers);
    const statusFor = (key: string) => mcpStatus?.servers?.find((s) => s.key === key);
    const addMcp = () => {
      const name = prompt('MCP 服务器名称（仅字母、数字、下划线、连字符）:')?.trim();
      if (!name) return;
      if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
        setToast('名称只能包含字母、数字、下划线和连字符');
        setTimeout(() => setToast(''), 3000);
        return;
      }
      if (config.mcpServers[name]) {
        setToast('该名称已存在');
        setTimeout(() => setToast(''), 3000);
        return;
      }
      update('mcpServers', {
        ...config.mcpServers,
        [name]: { type: 'stdio', is_active: true, command: '', args: [] },
      });
    };
    const delMcp = (name: string) => {
      if (confirm(`删除 MCP 服务器 "${name}"?`)) {
        const { [name]: _, ...rest } = config.mcpServers;
        update('mcpServers', rest);
      }
    };
    const renameMcp = (oldName: string, newName: string) => {
      const trimmed = newName.trim();
      if (trimmed === oldName || !trimmed) return;
      if (!/^[a-zA-Z0-9_-]+$/.test(trimmed)) {
        setToast('名称只能包含字母、数字、下划线和连字符');
        setTimeout(() => setToast(''), 3000);
        return;
      }
      if (config.mcpServers[trimmed]) {
        setToast('该名称已存在');
        setTimeout(() => setToast(''), 3000);
        return;
      }
      const entries = Object.entries(config.mcpServers);
      const newMap: Record<string, McpServerConfig> = {};
      for (const [k, v] of entries) {
        newMap[k === oldName ? trimmed : k] = v;
      }
      update('mcpServers', newMap);
    };
    const updMcp = (name: string, patch: Partial<McpServerConfig>) =>
      update('mcpServers', {
        ...config.mcpServers,
        [name]: { ...config.mcpServers[name], ...patch },
      });
    return (
      <div className="space-y-4">
        {/* MCP Status Summary */}
        {mcpStatus && mcpStatus.enabled && (
          <div className="flex items-center gap-3 p-3 rounded-lg bg-[var(--bg-tertiary)] text-xs">
            <div className="flex items-center gap-1.5">
              <span
                className={`inline-block w-2 h-2 rounded-full ${mcpStatus.connected_servers > 0 ? 'bg-green-400' : 'bg-gray-400'}`}
              />
              <span className="text-[var(--text-secondary)]">
                {mcpStatus.connected_servers}/{mcpStatus.total_servers} 已连接
              </span>
            </div>
            {mcpStatus.failed_servers > 0 && (
              <span className="text-red-400">{mcpStatus.failed_servers} 失败</span>
            )}
            <span className="text-[var(--text-muted)]">{mcpStatus.total_tools} 个工具</span>
            <button
              onClick={fetchMcpStatus}
              className="ml-auto px-2 py-1 rounded text-[var(--text-muted)] hover:text-[var(--accent-cyan)] transition-colors"
              title="刷新状态"
            >
              <RefreshCw className="h-3 w-3" />
            </button>
          </div>
        )}
        {entries.map(([name, s]) => {
          const st = statusFor(name);
          return (
            <div
              key={name}
              className="rounded-xl border border-[var(--border-color)] bg-[var(--bg-secondary)]/60 overflow-hidden"
            >
              {st && (
                <div className="flex items-center gap-2 px-4 py-2">
                  {st.connected ? (
                    <span className="inline-flex items-center gap-1 text-xs text-green-400">
                      <span className="w-2 h-2 rounded-full bg-green-400" /> {st.tool_count} 工具
                    </span>
                  ) : st.error ? (
                    <span
                      className="inline-flex items-center gap-1 text-xs text-red-400"
                      title={st.error}
                    >
                      <span className="w-2 h-2 rounded-full bg-red-400" /> 错误
                    </span>
                  ) : (
                    <span className="inline-flex items-center gap-1 text-xs text-gray-400">
                      <span className="w-2 h-2 rounded-full bg-gray-400" /> 未连接
                    </span>
                  )}
                </div>
              )}
              <MapEntryHeader
                name={name}
                onDelete={() => delMcp(name)}
                onRename={(n) => renameMcp(name, n)}
              />
              <div className="p-4 space-y-3">
                <Field label="传输类型">
                  <select
                    value={s.type}
                    onChange={(e) =>
                      updMcp(name, { type: e.target.value as McpServerConfig['type'] })
                    }
                    className={selectCls}
                  >
                    <option value="stdio">stdio (本地命令)</option>
                    <option value="streamableHttp">streamableHttp (HTTP)</option>
                  </select>
                </Field>
                <div className="flex items-center justify-between">
                  <span className="text-sm text-[var(--text-secondary)]">启用</span>
                  <Toggle checked={s.is_active} onChange={(v) => updMcp(name, { is_active: v })} />
                </div>
                <Field label="描述">
                  <input
                    value={s.description ?? ''}
                    onChange={(e) => updMcp(name, { description: e.target.value || undefined })}
                    className={inputCls}
                    placeholder="可选描述"
                  />
                </Field>
                {s.type === 'stdio' && (
                  <>
                    <Field label="命令" hint="如 npx, node, cargo, uv">
                      <input
                        value={s.command ?? ''}
                        onChange={(e) => updMcp(name, { command: e.target.value })}
                        className={inputCls}
                        placeholder="npx"
                      />
                    </Field>
                    <Field label="参数" hint="空格分隔">
                      <input
                        value={(s.args ?? []).join(' ')}
                        onChange={(e) =>
                          updMcp(name, { args: e.target.value ? e.target.value.split(/\s+/) : [] })
                        }
                        className={inputCls}
                        placeholder="-y @modelcontextprotocol/server-filesystem /tmp"
                      />
                    </Field>
                    <Field
                      label="工作目录 (cwd)"
                      hint="可选。子进程运行目录，常用于 uv/python 项目解析 pyproject.toml 或 venv"
                    >
                      <input
                        value={s.cwd ?? ''}
                        onChange={(e) => updMcp(name, { cwd: e.target.value || undefined })}
                        className={inputCls}
                        placeholder="E:\code_project\my-mcp-server"
                      />
                    </Field>
                    <Field label="环境变量" hint="KEY=VALUE，每行一个">
                      <textarea
                        value={Object.entries(s.env ?? {})
                          .map(([k, v]) => `${k}=${v}`)
                          .join('\n')}
                        onChange={(e) => {
                          const lines = e.target.value.split('\n').filter((l) => l.includes('='));
                          const env: Record<string, string> = {};
                          lines.forEach((l) => {
                            const [k, ...rest] = l.split('=');
                            if (k) env[k.trim()] = rest.join('=').trim();
                          });
                          updMcp(name, { env: Object.keys(env).length > 0 ? env : undefined });
                        }}
                        className={inputCls + ' min-h-[60px] resize-y font-mono text-xs'}
                        placeholder="API_KEY=xxx"
                      />
                    </Field>
                  </>
                )}
                {(s.type === 'streamableHttp' || s.type === 'http') && (
                  <>
                    <Field label="Base URL">
                      <input
                        value={s.base_url ?? ''}
                        onChange={(e) => updMcp(name, { base_url: e.target.value })}
                        className={inputCls}
                        placeholder="http://localhost:3000/mcp"
                      />
                    </Field>
                    <Field label="请求头" hint="KEY=VALUE，每行一个，支持 ${ENV_VAR}">
                      <textarea
                        value={Object.entries(s.headers ?? {})
                          .map(([k, v]) => `${k}=${v}`)
                          .join('\n')}
                        onChange={(e) => {
                          const lines = e.target.value.split('\n').filter((l) => l.includes('='));
                          const headers: Record<string, string> = {};
                          lines.forEach((l) => {
                            const [k, ...rest] = l.split('=');
                            if (k) headers[k.trim()] = rest.join('=').trim();
                          });
                          updMcp(name, {
                            headers: Object.keys(headers).length > 0 ? headers : undefined,
                          });
                        }}
                        className={inputCls + ' min-h-[60px] resize-y font-mono text-xs'}
                        placeholder="Authorization=Bearer ${TOKEN}"
                      />
                    </Field>
                  </>
                )}
              </div>
            </div>
          );
        })}
        <button
          onClick={addMcp}
          className="flex items-center gap-2 px-4 py-2.5 rounded-xl border border-dashed border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--accent-cyan)] hover:border-[var(--accent-cyan)]/30 transition-colors text-sm w-full justify-center"
        >
          <Plus className="h-4 w-4" /> 添加 MCP 服务器
        </button>
      </div>
    );
  };

  const renderChannels = () => {
    const entries = Object.entries(config.channels);
    const addChannel = () => {
      const name = prompt('渠道名称:')?.trim();
      if (name && !config.channels[name]) {
        update('channels', {
          ...config.channels,
          [name]: { type: 'feishu', enabled: false, app_id: '', app_secret: '' },
        });
      }
    };
    const delChannel = (name: string) => {
      if (confirm(`删除渠道 "${name}"?`)) {
        const { [name]: _, ...rest } = config.channels;
        update('channels', rest);
      }
    };
    const updChannel = (name: string, patch: Partial<ChannelConfig>) =>
      update('channels', { ...config.channels, [name]: { ...config.channels[name], ...patch } });
    const getChannelType = (ch: ChannelConfig): string => {
      if (ch.type) return ch.type;
      if ('app_id' in ch || 'app_secret' in ch) return 'feishu';
      if ('cred_path' in ch) return 'wechat';
      return 'feishu';
    };
    return (
      <div className="space-y-4">
        {entries.map(([name, ch]) => {
          const chType = getChannelType(ch);
          return (
            <div
              key={name}
              className="rounded-xl border border-[var(--border-color)] bg-[var(--bg-secondary)]/60 overflow-hidden"
            >
              <MapEntryHeader name={name} onDelete={() => delChannel(name)} />
              <div className="p-4 space-y-3">
                <Field label="渠道类型">
                  <select
                    value={chType}
                    onChange={(e) => updChannel(name, { type: e.target.value })}
                    className={selectCls}
                  >
                    <option value="feishu">飞书 (Feishu)</option>
                    <option value="wechat">微信 (WeChat)</option>
                  </select>
                </Field>
                <div className="flex items-center justify-between">
                  <span className="text-sm text-[var(--text-secondary)]">启用</span>
                  <Toggle
                    checked={!!ch.enabled}
                    onChange={(v) => updChannel(name, { enabled: v })}
                  />
                </div>
                {chType === 'feishu' && (
                  <>
                    <Field label="App ID">
                      <input
                        value={ch.app_id ?? ''}
                        onChange={(e) => updChannel(name, { app_id: e.target.value })}
                        className={inputCls}
                      />
                    </Field>
                    <Field label="App Secret">
                      <input
                        type="password"
                        value={ch.app_secret ?? ''}
                        onChange={(e) => updChannel(name, { app_secret: e.target.value })}
                        className={inputCls}
                      />
                    </Field>
                    <Field label="绑定 Agent" hint="留空使用 default">
                      <select
                        value={ch.agent ?? ''}
                        onChange={(e) => updChannel(name, { agent: e.target.value })}
                        className={selectCls}
                      >
                        <option value="">default</option>
                        {Object.keys(config.agents).map((a) => (
                          <option key={a} value={a}>
                            {a}
                          </option>
                        ))}
                      </select>
                    </Field>
                    <Field label="最大消息字符数">
                      <input
                        type="number"
                        value={ch.max_message_chars ?? 20000}
                        onChange={(e) => updChannel(name, { max_message_chars: +e.target.value })}
                        className={inputCls}
                      />
                    </Field>
                    <Field label="回复上下文最大字符数">
                      <input
                        type="number"
                        value={ch.reply_context_max_chars ?? 20000}
                        onChange={(e) =>
                          updChannel(name, { reply_context_max_chars: +e.target.value })
                        }
                        className={inputCls}
                      />
                    </Field>
                  </>
                )}
                {chType === 'wechat' && (
                  <>
                    <Field label="凭证文件路径">
                      <input
                        value={ch.cred_path ?? ''}
                        onChange={(e) => updChannel(name, { cred_path: e.target.value })}
                        className={inputCls}
                        placeholder="~/.picobot/wechat/credentials.json"
                      />
                    </Field>
                    <Field label="Base URL">
                      <input
                        value={ch.base_url ?? 'https://ilinkai.weixin.qq.com'}
                        onChange={(e) => updChannel(name, { base_url: e.target.value })}
                        className={inputCls}
                      />
                    </Field>
                    <Field label="绑定 Agent" hint="留空使用 default">
                      <select
                        value={ch.agent ?? ''}
                        onChange={(e) => updChannel(name, { agent: e.target.value })}
                        className={selectCls}
                      >
                        <option value="">default</option>
                        {Object.keys(config.agents).map((a) => (
                          <option key={a} value={a}>
                            {a}
                          </option>
                        ))}
                      </select>
                    </Field>
                    <div className="flex items-center justify-between">
                      <span className="text-sm text-[var(--text-secondary)]">强制重新登录</span>
                      <Toggle
                        checked={!!ch.force_login}
                        onChange={(v) => updChannel(name, { force_login: v })}
                      />
                    </div>
                  </>
                )}
              </div>
            </div>
          );
        })}
        <button
          onClick={addChannel}
          className="flex items-center gap-2 px-4 py-2.5 rounded-xl border border-dashed border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--accent-cyan)] hover:border-[var(--accent-cyan)]/30 transition-colors text-sm w-full justify-center"
        >
          <Plus className="h-4 w-4" /> 添加渠道
        </button>
      </div>
    );
  };

  const renderContent = () => {
    switch (activeTab) {
      case 'connection':
        return renderConnection();
      case 'gateway':
        return renderGateway();
      case 'providers':
        return renderProviders();
      case 'models':
        return renderModels();
      case 'agents':
        return renderAgents();
      case 'time':
        return renderTime();
      case 'scheduler':
        return renderScheduler();
      case 'skills':
        return renderSkills();
      case 'tools':
        return renderTools();
      case 'memory':
        return renderMemory();
      case 'image':
        return renderImage();
      case 'subagents':
        return renderSubagents();
      case 'experts':
        return renderExperts();
      case 'mcp':
        return renderMcp();
      case 'channels':
        return renderChannels();
    }
  };

  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm animate-[fadeIn_0.15s_ease-out]">
      <div
        className="relative flex flex-col w-[92vw] max-w-4xl h-[85vh] rounded-2xl border border-[var(--border-color)] bg-[var(--bg-primary)] shadow-2xl overflow-hidden animate-[scaleIn_0.2s_ease-out]"
        onClick={(e) => e.stopPropagation()}
      >
        {/* Header */}
        <div className="shrink-0 flex items-center gap-3 px-6 py-4 border-b border-[var(--border-color)] bg-[var(--bg-secondary)]/80 backdrop-blur-md">
          <Settings className="h-5 w-5 text-[var(--accent-cyan)]" />
          <span className="text-lg font-semibold text-[var(--text-primary)]">系统配置</span>
          {dirty && (
            <span className="text-xs text-amber-400 bg-amber-500/10 px-2 py-0.5 rounded-full">
              未保存
            </span>
          )}
          <div className="flex-1" />
          <button
            onClick={handleClose}
            className="p-2 rounded-lg text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--overlay-hover)] transition-colors"
            title="关闭 (Esc)"
          >
            <X className="h-5 w-5" />
          </button>
        </div>

        {/* Body */}
        <div className="flex flex-1 min-h-0">
          {/* Left sidebar tabs */}
          <div className="shrink-0 w-48 border-r border-[var(--border-color)] bg-[var(--bg-secondary)]/40 overflow-y-auto py-2">
            {TABS.map((tab) => {
              const Icon = tab.icon;
              const active = activeTab === tab.id;
              return (
                <button
                  key={tab.id}
                  onClick={() => setActiveTab(tab.id)}
                  className={`w-full flex items-center gap-2.5 px-4 py-2.5 text-sm transition-all relative ${
                    active
                      ? 'text-[var(--accent-cyan)] bg-[var(--accent-cyan)]/5'
                      : 'text-[var(--text-muted)] hover:text-[var(--text-secondary)] hover:bg-[var(--overlay-hover)]'
                  }`}
                >
                  {active && (
                    <span className="absolute left-0 top-1 bottom-1 w-[2px] rounded-r bg-[var(--accent-cyan)]" />
                  )}
                  <Icon className="h-4 w-4 shrink-0" />
                  <span>{tab.label}</span>
                </button>
              );
            })}
          </div>

          {/* Right content */}
          <div className="flex-1 overflow-y-auto p-6">{renderContent()}</div>
        </div>

        {/* Footer */}
        <div className="shrink-0 px-6 py-3 border-t border-[var(--border-color)] bg-[var(--bg-secondary)]/80 backdrop-blur-md flex items-center gap-3">
          {error && <span className="text-sm text-red-400 truncate max-w-xs">{error}</span>}
          <div className="flex-1" />
          <button
            onClick={handleClose}
            className="px-4 py-2 rounded-lg text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--overlay-hover)] transition-colors"
          >
            取消
          </button>
          <button
            onClick={handleSave}
            disabled={saving || !dirty}
            className="flex items-center gap-2 px-5 py-2 rounded-lg text-sm font-medium text-white bg-[var(--accent-cyan)]/20 border border-[var(--accent-cyan)]/30 hover:bg-[var(--accent-cyan)]/30 hover:border-[var(--accent-cyan)]/50 transition-all disabled:opacity-40 disabled:cursor-not-allowed"
          >
            {saving ? <Loader2 className="h-4 w-4 animate-spin" /> : <Save className="h-4 w-4" />}
            {saving ? '保存中...' : '保存配置'}
          </button>
        </div>

        {/* Toast */}
        {toast && (
          <div className="absolute top-20 left-1/2 -translate-x-1/2 z-10 flex items-center gap-2 px-5 py-3 rounded-xl bg-emerald-500/15 border border-emerald-500/30 text-emerald-400 text-sm shadow-lg backdrop-blur-md animate-[fadeIn_0.2s_ease-out]">
            {restarting ? (
              <Loader2 className="h-4 w-4 animate-spin" />
            ) : (
              <CheckCircle className="h-4 w-4" />
            )}
            {toast}
          </div>
        )}

        {/* Restart confirmation dialog */}
        {showRestartDialog && (
          <div className="absolute inset-0 z-20 flex items-center justify-center bg-black/50 backdrop-blur-sm rounded-2xl">
            <div className="bg-[var(--bg-secondary)] border border-[var(--border-color)] rounded-xl p-6 max-w-sm mx-4 shadow-2xl animate-[scaleIn_0.2s_ease-out]">
              <div className="flex items-center gap-3 mb-4">
                <div className="flex items-center justify-center w-10 h-10 rounded-full bg-[var(--accent-cyan)]/10">
                  <RefreshCw className="h-5 w-5 text-[var(--accent-cyan)]" />
                </div>
                <div>
                  <h3 className="text-sm font-semibold text-[var(--text-primary)]">配置已保存</h3>
                  <p className="text-xs text-[var(--text-muted)]">是否立即重启服务使配置生效？</p>
                </div>
              </div>
              <div className="flex gap-3 justify-end">
                <button
                  onClick={() => setShowRestartDialog(false)}
                  className="px-4 py-2 rounded-lg text-sm text-[var(--text-secondary)] hover:text-[var(--text-primary)] hover:bg-[var(--overlay-hover)] transition-colors"
                >
                  稍后
                </button>
                <button
                  onClick={handleRestart}
                  className="flex items-center gap-2 px-4 py-2 rounded-lg text-sm font-medium text-white bg-[var(--accent-cyan)]/20 border border-[var(--accent-cyan)]/30 hover:bg-[var(--accent-cyan)]/30 hover:border-[var(--accent-cyan)]/50 transition-all"
                >
                  <RefreshCw className="h-4 w-4" />
                  立即重启
                </button>
              </div>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}
