import { useEffect, useRef, useState } from 'react';
import { Button, Input, Space, Tag, App as AntApp, Popconfirm, Empty, Collapse, Switch, Drawer, Slider, InputNumber, Upload, Tooltip, Modal, Image as AntImage, Divider, Dropdown, Select } from 'antd';
import { SettingOutlined, ApiOutlined, EditOutlined, DeleteOutlined, PaperClipOutlined, BookOutlined, ArrowUpOutlined, CloseOutlined, DownOutlined } from '@ant-design/icons';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import ReactMarkdown from 'react-markdown';
import {
  Agent,
  AgentAPI,
  BranchInfo,
  ChatAPI,
  ChatAttachment,
  ChatAttachmentsAPI,
  ChatMessage,
  ImageAPI,
  ModelOverrides,
  RetrievedSnippet,
  SessionAPI,
  regenerateMessage,
  streamChat,
  ToolCallTrace
} from '../api';
import SessionSidebar from '../components/SessionSidebar';
import McpResourcesDrawer from '../components/McpResourcesDrawer';
import PromptLibraryPage from './PromptLibraryPage';

interface StreamingState {
  active: boolean;
  reasoningText: string;
  answerText: string;
  errorMessage: string | null;
  retryInfo: any | null;
  retrieved: RetrievedSnippet[];
  toolCalls: ToolCallTrace[];
}

const parseAgentModels = (value?: string) =>
  String(value || '')
    .split(',')
    .map((item) => item.trim())
    .filter(Boolean);

export default function ChatPage() {
  const { id } = useParams();
  const navigate = useNavigate();
  const [searchParams, setSearchParams] = useSearchParams();
  const { message: msg } = AntApp.useApp();
  const [agent, setAgent] = useState<Agent | null>(null);
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [branches, setBranches] = useState<Record<string, BranchInfo>>({});
  const [input, setInput] = useState('');
  const [sending, setSending] = useState(false);
  const [useStream, setUseStream] = useState(true);
  const [sessionId, setSessionId] = useState<string | null>(null);
  const [highlightId, setHighlightId] = useState<string | null>(null);
  const [sessionRefresh, setSessionRefresh] = useState(0);
  const [agentList, setAgentList] = useState<Agent[]>([]);
  const [historyDrawerOpen, setHistoryDrawerOpen] = useState(false);
  const [mcpDrawerOpen, setMcpDrawerOpen] = useState(false);
  const [paramsDrawerOpen, setParamsDrawerOpen] = useState(false);
  const [tplDrawerOpen, setTplDrawerOpen] = useState(false);
  const [overrides, setOverrides] = useState<ModelOverrides>({});
  const [attachments, setAttachments] = useState<ChatAttachment[]>([]);
  const [imageUrls, setImageUrls] = useState<string[]>([]);
  const [uploadingAtt, setUploadingAtt] = useState(false);
  const [streaming, setStreaming] = useState<StreamingState>({
    active: false,
    reasoningText: '',
    answerText: '',
    errorMessage: null,
    retryInfo: null,
    retrieved: [],
    toolCalls: []
  });
  const bodyRef = useRef<HTMLDivElement>(null);
  const abortRef = useRef<AbortController | null>(null);
  const creatingSessionRef = useRef(false);
  const autoScrollRef = useRef(true);

  // URL 参数 ?session=xxx&msg=yyy
  useEffect(() => {
    const s = searchParams.get('session');
    const m = searchParams.get('msg');
    if (s) {
      setSessionId(s);
      if (m) setHighlightId(m);
      const next = new URLSearchParams(searchParams);
      next.delete('session');
      next.delete('msg');
      setSearchParams(next, { replace: true });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [searchParams]);

  const scrollBottom = (force = false) => {
    if (!force && !autoScrollRef.current) return;
    requestAnimationFrame(() => {
      bodyRef.current?.scrollTo({ top: bodyRef.current.scrollHeight, behavior: 'smooth' });
    });
  };

  useEffect(() => {
    const el = bodyRef.current;
    if (!el) return;
    const onScroll = () => {
      const distance = el.scrollHeight - el.scrollTop - el.clientHeight;
      autoScrollRef.current = distance < 32;
    };
    el.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    return () => el.removeEventListener('scroll', onScroll);
  }, []);

  const loadAgent = async () => {
    if (!id) {
      setAgent(null);
      setMessages([]);
      return;
    }
    const a = await AgentAPI.detail(id);
    setAgent(a);
    const firstModel = parseAgentModels(a.model)[0];
    if (firstModel) {
      setOverrides((o) => ({ ...o, model: o.model || firstModel }));
    }
  };

  const loadAgentList = async () => {
    try {
      const list = await AgentAPI.list();
      setAgentList(list);
    } catch (e) {
      // ignore
    }
  };

  const loadMessages = async () => {
    if (!id || !sessionId) return;
    const his = await ChatAPI.history(id, sessionId);
    setMessages(Array.isArray(his.messages) ? his.messages : []);
    setBranches(his.branches || {});
    requestAnimationFrame(() => {
      if (highlightId) {
        const el = document.getElementById('msg-' + highlightId);
        if (el) {
          el.scrollIntoView({ behavior: 'smooth', block: 'center' });
          setTimeout(() => setHighlightId(null), 3000);
          return;
        }
      }
      scrollBottom();
    });
  };

  useEffect(() => {
    loadAgentList();
  }, []);

  useEffect(() => {
    if (!id) {
      setAgent(null);
      setMessages([]);
      setOverrides({});
      setSessionId(null);
      return () => abortRef.current?.abort();
    }
    loadAgent();
    setOverrides({});
    return () => abortRef.current?.abort();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [id]);

  useEffect(() => {
    if (!id || sessionId || creatingSessionRef.current) return;
    creatingSessionRef.current = true;
    SessionAPI.create(id)
      .then((s) => setSessionId(s.id))
      .catch(() => msg.error('创建会话失败'))
      .finally(() => {
        creatingSessionRef.current = false;
      });
  }, [id, msg, sessionId]);

  useEffect(() => {
    if (!id) return;
    loadMessages();
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [sessionId, highlightId, id]);

  /** 把附件文本拼成 system 注入字符串 */
  const buildAttachmentsText = () => {
    if (!attachments.length) return '';
    return attachments
      .map(
        (a, i) =>
          `### 附件 ${i + 1}: ${a.name}${a.truncated ? ' (已截断)' : ''}\n${a.text}`
      )
      .join('\n\n---\n\n');
  };

  const handleSendStream = async (text: string) => {
    if (!id || !sessionId) return;
    const tempUser: ChatMessage = {
      id: 'tmp-' + Date.now(),
      role: 'user',
      content: text,
      createdAt: Date.now()
    };
    setMessages((m) => [...(m || []), tempUser]);
    setStreaming({
      active: true,
      reasoningText: '',
      answerText: '',
      errorMessage: null,
      retryInfo: null,
      retrieved: [],
      toolCalls: []
    });
    scrollBottom(true);

    abortRef.current?.abort();
    const ctrl = new AbortController();
    abortRef.current = ctrl;

    const model = overrides.model || parseAgentModels(agent?.model)[0] || '';
    const attText = buildAttachmentsText();
    const content = attText ? `${text}\n\n${attText}` : text;

    try {
      await streamChat(
        id,
        content,
        {
          onMeta: (m) => setStreaming((s) => ({ ...s, retrieved: m.retrieved || [] })),
          onRetry: (data) => {
            setStreaming((s) => ({ ...s, retryInfo: data }));
            if (data?.stage === 'fallback_model' && data?.toModel) {
              setOverrides((o) => ({ ...o, model: String(data.toModel) }));
            }
          },
          onReasoningDelta: (chunk) =>
            setStreaming((s) => {
              const next = { ...s, reasoningText: s.reasoningText + chunk };
              scrollBottom();
              return next;
            }),
          onDelta: (chunk) =>
            setStreaming((s) => {
              const next = { ...s, answerText: s.answerText + chunk };
              scrollBottom();
              return next;
            }),
          onToolCall: (data) =>
            setStreaming((s) => ({
              ...s,
              toolCalls: [...s.toolCalls, { name: data.name, args: data.args, result: { pending: true } }]
            })),
          onToolResult: (data) =>
            setStreaming((s) => {
              const list = [...s.toolCalls];
              for (let i = list.length - 1; i >= 0; i--) {
                if (list[i].name === data.name && (list[i].result as any)?.pending) {
                  list[i] = { ...list[i], result: data.result };
                  break;
                }
              }
              return { ...s, toolCalls: list };
            }),
          onDone: (data) => {
            setStreaming((s) => {
              const assistant: any = data.assistant;
              const reasoningText =
                assistant?.reasoning ||
                assistant?.meta?.reasoning ||
                assistant?.meta?.reasoningText ||
                s.reasoningText;
              const nextAssistant: ChatMessage = {
                ...data.assistant,
                meta: {
                  ...(data.assistant.meta || {}),
                  retrieved: data.assistant.meta?.retrieved || s.retrieved,
                  toolCalls: data.assistant.meta?.toolCalls || s.toolCalls,
                  reasoning: reasoningText || undefined
                }
              };
              setMessages((m) => [...(m || []).filter((x) => x.id !== tempUser.id), data.user, nextAssistant]);
              return {
                active: false,
                reasoningText: '',
                answerText: '',
                errorMessage: null,
                retryInfo: null,
                retrieved: [],
                toolCalls: []
              };
            });
            setSessionRefresh((t) => t + 1);
            setAttachments([]); // 用完即清
            setImageUrls([]);
            scrollBottom(true);
          },
          onAborted: (data) => {
            // 已停止：保留已生成内容；user 消息也留下（用 tempUser 占位）
            const assistant: ChatMessage = {
              ...data.assistant,
              meta: {
                ...(data.assistant.meta || {}),
                aborted: true
              }
            };
            setMessages((m) => [
              ...(m || []).filter((x) => x.id !== tempUser.id),
              { ...tempUser, id: 'u-' + data.assistant.id }, // 占位
              assistant
            ]);
            setStreaming({
              active: false,
              reasoningText: '',
              answerText: '',
              errorMessage: null,
              retryInfo: null,
              retrieved: [],
              toolCalls: []
            });
            setSessionRefresh((t) => t + 1);
            // 重新拉一次确保和后端一致（user 真实 id 在那边）
            loadMessages();
          },
          onError: (errMsg) => {
            msg.error('流式失败：' + errMsg);
            setMessages((m) => (m || []).filter((x) => x.id !== tempUser.id));
            setStreaming({
              active: false,
              reasoningText: '',
              answerText: '',
              errorMessage: errMsg,
              retryInfo: null,
              retrieved: [],
              toolCalls: []
            });
          }
        },
        ctrl.signal,
        sessionId,
        model,
        imageUrls
      );
    } catch (e: any) {
      if (e?.name !== 'AbortError') {
        msg.error('请求失败：' + (e?.message ?? e));
      }
      setMessages((m) => (m || []).filter((x) => x.id !== tempUser.id));
      setStreaming({
        active: false,
        reasoningText: '',
        answerText: '',
        errorMessage: e?.message ?? String(e),
        retryInfo: null,
        retrieved: [],
        toolCalls: []
      });
    }
  };

  const handleSendNonStream = async (text: string) => {
    if (!id || !sessionId) return;
    const tempUser: ChatMessage = {
      id: 'tmp-' + Date.now(),
      role: 'user',
      content: text,
      createdAt: Date.now()
    };
    setMessages((m) => [...(m || []), tempUser]);
    scrollBottom(true);
    const attText = buildAttachmentsText();
    const content = attText ? `${text}\n\n${attText}` : text;
    const model = overrides.model || parseAgentModels(agent?.model)[0] || '';
    try {
      const res = await ChatAPI.send(
        id,
        content,
        sessionId,
        model,
        imageUrls
      );
      setMessages((m) => [...(m || []).filter((x) => x.id !== tempUser.id), res.user, res.assistant]);
      setSessionRefresh((t) => t + 1);
      setAttachments([]);
      setImageUrls([]);
      scrollBottom();
    } catch (e: any) {
      msg.error('发送失败：' + (e?.message ?? e));
      setMessages((m) => (m || []).filter((x) => x.id !== tempUser.id));
    }
  };

  const handleSend = async () => {
    const text = input.trim();
    if (!text || !id || sending || !sessionId) return;
    setInput('');
    setSending(true);
    try {
      if (useStream) await handleSendStream(text);
      else await handleSendNonStream(text);
    } finally {
      setSending(false);
    }
  };

  const handleStop = () => {
    abortRef.current?.abort();
    setSending(false);
  };

  const handleClear = async () => {
    if (!id || !sessionId) return;
    await ChatAPI.clear(id, sessionId);
    setMessages([]);
    setBranches({});
    msg.success('对话已清空');
  };

  const handleRegenerate = async (assistantId: string) => {
    if (!id || sending) return;
    setSending(true);
    setStreaming({
      active: true,
      reasoningText: '',
      answerText: '',
      errorMessage: null,
      retryInfo: null,
      retrieved: [],
      toolCalls: []
    });
    abortRef.current?.abort();
    const ctrl = new AbortController();
    abortRef.current = ctrl;
    try {
      await regenerateMessage(
        id,
        assistantId,
        {
          onMeta: (m) => setStreaming((s) => ({ ...s, retrieved: m.retrieved || [] })),
          onRetry: (data) => {
            setStreaming((s) => ({ ...s, retryInfo: data }));
            if (data?.stage === 'fallback_model' && data?.toModel) {
              setOverrides((o) => ({ ...o, model: String(data.toModel) }));
            }
          },
          onReasoningDelta: (chunk) =>
            setStreaming((s) => {
              const next = { ...s, reasoningText: s.reasoningText + chunk };
              scrollBottom();
              return next;
            }),
          onDelta: (chunk) =>
            setStreaming((s) => {
              const next = { ...s, answerText: s.answerText + chunk };
              scrollBottom();
              return next;
            }),
          onToolCall: (data) =>
            setStreaming((s) => ({
              ...s,
              toolCalls: [...s.toolCalls, { name: data.name, args: data.args, result: { pending: true } }]
            })),
          onToolResult: (data) =>
            setStreaming((s) => {
              const list = [...s.toolCalls];
              for (let i = list.length - 1; i >= 0; i--) {
                if (list[i].name === data.name && (list[i].result as any)?.pending) {
                  list[i] = { ...list[i], result: data.result };
                  break;
                }
              }
              return { ...s, toolCalls: list };
            }),
          onDone: () => {
            setStreaming({
              active: false,
              reasoningText: '',
              answerText: '',
              errorMessage: null,
              retryInfo: null,
              retrieved: [],
              toolCalls: []
            });
            loadMessages();
          },
          onAborted: () => {
            setStreaming({
              active: false,
              reasoningText: '',
              answerText: '',
              errorMessage: null,
              retryInfo: null,
              retrieved: [],
              toolCalls: []
            });
            loadMessages();
          },
          onError: (errMsg) => {
            msg.error('重新生成失败：' + errMsg);
            setStreaming({
              active: false,
              reasoningText: '',
              answerText: '',
              errorMessage: errMsg,
              retryInfo: null,
              retrieved: [],
              toolCalls: []
            });
            loadMessages();
          }
        },
        ctrl.signal,
        overrides
      );
    } finally {
      setSending(false);
    }
  };

  const handleSwitchBranch = async (userMsgId: string, branchId: string) => {
    if (!id) return;
    await ChatAPI.switchBranch(id, userMsgId, branchId);
    await loadMessages();
  };

  const handleAttach = async (files: File[]) => {
    if (!files.length) return;
    setUploadingAtt(true);
    try {
      // v1.0: 自动按类型分流。图片 → 图床；其他 → 文本附件
      const images = files.filter((f) => f.type.startsWith('image/'));
      const docs = files.filter((f) => !f.type.startsWith('image/'));
      let imgN = 0, docN = 0;
      if (images.length) {
        const r = await ImageAPI.upload(images);
        setImageUrls((arr) => [...arr, ...r.files.map((f) => f.url)]);
        imgN = r.files.length;
      }
      if (docs.length) {
        const r = await ChatAttachmentsAPI.upload(docs);
        setAttachments((a) => [...a, ...r.files]);
        docN = r.files.length;
      }
      const parts = [];
      if (imgN) parts.push(`${imgN} 张图片`);
      if (docN) parts.push(`${docN} 个文档`);
      if (parts.length) msg.success(`已附加 ${parts.join(' + ')}`);
    } catch (e: any) {
      msg.error('附件上传失败：' + (e?.message ?? e));
    } finally {
      setUploadingAtt(false);
    }
  };

  const isImageUrl = (url: string) => url?.startsWith('http') || url?.startsWith('/');
  const agentModel = agent?.model || '';
  const agentModels = parseAgentModels(agentModel);
  const modelOptions = agentModels.map((modelName) => ({
    value: modelName,
    label: modelName
  }));
  const activeModelValue = overrides.model || '';

  return (
    <div className="chat-shell">
      {/* 1. 二级侧边栏：智能体列表 */}
      <aside className="chat-side">
        <div style={{ padding: '16px', borderBottom: '1px solid var(--color-border)' }}>
          <Button block type="dashed" onClick={() => navigate('/agents/new')}>+ 创建智能体</Button>
        </div>
        <div style={{ flex: 1, overflowY: 'auto', padding: '8px 0' }}>
          {agentList.map((a) => {
            const isActive = a.id === id;
            return (
              <div
                key={a.id}
                onClick={() => navigate(`/chat/${a.id}`)}
                style={{
                  display: 'flex',
                  alignItems: 'center',
                  gap: 12,
                  padding: '10px 16px',
                  cursor: 'pointer',
                  background: isActive ? 'var(--color-surface-2)' : 'transparent',
                  borderLeft: `3px solid ${isActive ? 'var(--color-brand)' : 'transparent'}`,
                  transition: 'background 0.2s'
                }}
              >
                <div
                  style={{
                    width: 32,
                    height: 32,
                    borderRadius: '50%',
                    background: a.avatar || 'var(--gradient-brand)',
                    color: '#fff',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    fontWeight: 700,
                    fontSize: 14,
                    overflow: 'hidden'
                  }}
                >
                  {isImageUrl(a.avatar) ? (
                    <img src={a.avatar} className="w-full h-full object-cover" alt="avatar" />
                  ) : (
                    (a.name?.charAt(0) || '?').toUpperCase()
                  )}
                </div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontWeight: isActive ? 600 : 500, fontSize: 14, color: 'var(--color-text)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                    {a.name}
                  </div>
                </div>
              </div>
            );
          })}
        </div>
      </aside>

      {/* 2. 主聊天区 */}
      <section className="chat-main">
        {!agent ? (
          <div style={{ flex: 1, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <Empty description="请在左侧选择一个智能体开始对话" />
          </div>
        ) : (
          <>
            {/* Header */}
            <div className="chat-header">
              <div>
                <div style={{ fontWeight: 600, fontSize: 16, color: 'var(--color-text)' }}>{agent.name}</div>
                <div style={{ fontSize: 12, color: 'var(--color-text-tertiary)' }}>
                  {agent.model || '默认模型'} · T={agent.temperature}
                </div>
              </div>
              <Space>
                <div style={{ display: 'flex', alignItems: 'center', gap: 6, marginRight: 12 }}>
                  <span style={{ fontSize: 12, color: 'var(--color-text-secondary)' }}>流式输出</span>
                  <Switch size="small" checked={useStream} onChange={setUseStream} />
                </div>
                <Button size="small" onClick={() => setHistoryDrawerOpen(true)}>历史对话</Button>
                <Dropdown
                  menu={{
                    items: [
                      { key: 'params', label: '模型参数', icon: <SettingOutlined />, onClick: () => setParamsDrawerOpen(true) },
                      { key: 'mcp', label: 'MCP 资源', icon: <ApiOutlined />, onClick: () => setMcpDrawerOpen(true) },
                      { key: 'edit', label: '管理 Agent', icon: <EditOutlined />, onClick: () => navigate(`/agents/${id}`) },
                      { type: 'divider' },
                      { key: 'clear', label: '清空对话', icon: <DeleteOutlined />, danger: true, onClick: () => {
                          Modal.confirm({
                            title: '清空当前会话所有消息？',
                            onOk: handleClear
                          });
                        }
                      }
                    ]
                  }}
                >
                  <Button size="small">更多 <DownOutlined /></Button>
                </Dropdown>
              </Space>
            </div>

            {/* Body */}
            <div ref={bodyRef} className="chat-body">
          <div className="messages-container">
            {messages.length === 0 && !streaming.active ? (
              <div style={{ textAlign: 'center', marginTop: 120 }}>
                <div
                  style={{
                  width: 68, height: 68, borderRadius: '50%',
                  background: agent.avatar || 'var(--gradient-brand)',
                  color: '#fff', display: 'flex', alignItems: 'center',
                  justifyContent: 'center', fontWeight: 700, fontSize: 32,
                  margin: '0 auto 20px', boxShadow: 'var(--shadow-lg)',
                  overflow: 'hidden'
                }}>
                  {isImageUrl(agent.avatar) ? (
                    <img src={agent.avatar} className="w-full h-full object-cover" alt="avatar" />
                  ) : (
                    (agent.name?.charAt(0) || '?').toUpperCase()
                  )}
                </div>
                <h2
                  style={{
                    fontSize: 28,
                    fontWeight: 700,
                    color: 'var(--color-text)',
                    marginBottom: 8,
                    letterSpacing: '-0.02em'
                  }}
                >
                  你好，今天想一起完成什么？
                </h2>
                <p style={{ color: 'var(--color-text-secondary)', fontSize: 15, lineHeight: 1.7 }}>
                  {agent.description || '我是你的专属 AI 助手，随时准备为你服务。'}
                </p>
              </div>
            ) : (
              <>
                {messages.map((m) => (
                  <MessageItem
                    key={m.id}
                    message={m}
                    highlighted={highlightId === m.id}
                    branch={m.role === 'assistant' && m.parentId ? branches[m.parentId] : undefined}
                    busy={sending}
                    onRegenerate={handleRegenerate}
                    onSwitchBranch={handleSwitchBranch}
                    onCopy={(text) => {
                      navigator.clipboard?.writeText(text).then(() => msg.success('已复制'));
                    }}
                  />
                ))}
                {streaming.active && (
                  <div style={{ marginBottom: 24 }}>
                    <div className="bubble assistant">
                      <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
                        {!!streaming.retryInfo?.message && (
                          <div
                            style={{
                              padding: '8px 10px',
                              borderRadius: 10,
                              background: 'rgba(59, 130, 246, 0.08)',
                              border: '1px solid rgba(59, 130, 246, 0.18)'
                            }}
                          >
                            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
                              <span style={{ fontSize: 12.5, color: 'var(--color-text)', fontWeight: 600 }}>
                                {streaming.retryInfo.stage === 'fallback_model' ? '自动切换模型' : '自动重试'}
                              </span>
                              {streaming.retryInfo.stage === 'fallback_model' ? (
                                <Tag color="processing" style={{ marginInlineEnd: 0 }}>
                                  {String(streaming.retryInfo.fromModel || '')} → {String(streaming.retryInfo.toModel || '')}
                                </Tag>
                              ) : (
                                <Tag color="processing" style={{ marginInlineEnd: 0 }}>
                                  {String(streaming.retryInfo.model || '')}
                                  {streaming.retryInfo.attempt ? ` · 第${streaming.retryInfo.attempt}次` : ''}
                                </Tag>
                              )}
                            </div>
                            <div style={{ marginTop: 4, fontSize: 12.5, color: 'var(--color-text-secondary)', lineHeight: 1.55 }}>
                              {String(streaming.retryInfo.message)}
                            </div>
                            {!!streaming.retryInfo.reason && (
                              <div style={{ marginTop: 4, fontSize: 12, color: 'var(--color-text-tertiary)', lineHeight: 1.5 }}>
                                {String(streaming.retryInfo.reason)}
                              </div>
                            )}
                          </div>
                        )}
                        <div>
                          <div style={{ fontSize: 12, color: 'var(--color-text-tertiary)', marginBottom: 4 }}>
                            推理过程
                          </div>
                          {streaming.reasoningText ? (
                            <ReactMarkdown>{streaming.reasoningText + '▍'}</ReactMarkdown>
                          ) : (
                            <span style={{ color: 'var(--color-text-tertiary)' }}>等待推理…</span>
                          )}
                        </div>
                        <Divider style={{ margin: '6px 0' }} />
                        <div>
                          <div style={{ fontSize: 12, color: 'var(--color-text-tertiary)', marginBottom: 4 }}>
                            正式回答
                          </div>
                          {streaming.answerText ? (
                            <ReactMarkdown>{streaming.answerText + '▍'}</ReactMarkdown>
                          ) : (
                            <span style={{ color: 'var(--color-text-tertiary)' }}>等待输出…</span>
                          )}
                        </div>
                      </div>
                    </div>
                    {(streaming.retrieved.length > 0 || streaming.toolCalls.length > 0) && (
                      <div style={{ maxWidth: '85%' }}>
                        {streaming.retrieved.length > 0 && (
                          <RetrievedView retrieved={streaming.retrieved} liveStyle />
                        )}
                        {streaming.toolCalls.length > 0 && (
                          <ToolCallView calls={streaming.toolCalls} liveStyle />
                        )}
                      </div>
                    )}
                  </div>
                )}
              </>
            )}
          </div>
        </div>

        {/* Input Footer */}
        <div className="chat-input-wrapper">
          {/* Attachments Preview */}
          <div style={{ display: 'flex', flexWrap: 'wrap', gap: 8, marginBottom: 8 }}>
            {attachments.map((a, i) => (
              <Tag
                key={i}
                color="blue"
                closable
                style={{ borderRadius: 6, padding: '4px 8px' }}
                onClose={() => setAttachments((arr) => arr.filter((_, j) => j !== i))}
              >
                📎 {a.name}
              </Tag>
            ))}
            {imageUrls.map((u, i) => (
              <div key={i} style={{ position: 'relative' }}>
                <AntImage
                  src={u}
                  width={48}
                  height={48}
                  style={{ objectFit: 'cover', borderRadius: 8, border: '1px solid var(--color-border)' }}
                />
                <Button
                  size="small"
                  type="primary"
                  shape="circle"
                  icon={<CloseOutlined style={{ fontSize: 8 }} />}
                  style={{
                    position: 'absolute', top: -6, right: -6,
                    width: 16, height: 16, display: 'flex', alignItems: 'center', justifyContent: 'center'
                  }}
                  onClick={() => setImageUrls((arr) => arr.filter((_, j) => j !== i))}
                />
              </div>
            ))}
          </div>

          <div className="chat-input-card">
            <div className="chat-input-stack">
              <Input.TextArea
                value={input}
                onChange={(e) => setInput(e.target.value)}
                placeholder="问我任何问题..."
                autoSize={{ minRows: 3, maxRows: 10 }}
                onKeyDown={(e) => {
                  if (e.key !== 'Enter') return;
                  if ((e as any).isComposing) return;

                  if (e.metaKey || e.ctrlKey) {
                    e.preventDefault();
                    const el = e.currentTarget;
                    const start = el.selectionStart ?? input.length;
                    const end = el.selectionEnd ?? input.length;
                    const next = input.slice(0, start) + '\n' + input.slice(end);
                    setInput(next);
                    requestAnimationFrame(() => {
                      el.selectionStart = el.selectionEnd = start + 1;
                    });
                    return;
                  }

                  if (!e.shiftKey && !e.altKey) {
                    e.preventDefault();
                    handleSend();
                  }
                }}
                className="chat-input-textarea"
                disabled={sending}
              />

              <div className="chat-input-toolbar">
                <div className="chat-input-toolbar-left">
                  <Select
                    value={activeModelValue || undefined}
                    className="chat-model-select"
                    popupMatchSelectWidth={false}
                    options={modelOptions}
                    suffixIcon={<DownOutlined className="chat-model-select-arrow" />}
                    placeholder="选择模型"
                    onChange={(value) => setOverrides((o) => ({ ...o, model: String(value) }))}
                  />

                  <Upload
                    className="chat-upload"
                    multiple
                    beforeUpload={(_f, files) => {
                      handleAttach(files as File[]);
                      return false;
                    }}
                    showUploadList={false}
                    accept=".txt,.md,.markdown,.json,.csv,.pdf,.docx,.html,.htm,image/png,image/jpeg,image/webp,image/gif"
                  >
                    <Button type="text" className="chat-tool-button" icon={<PaperClipOutlined style={{ fontSize: 18 }} />} />
                  </Upload>
                  <Button
                    type="text"
                    className="chat-tool-button"
                    icon={<BookOutlined style={{ fontSize: 18 }} />}
                    onClick={() => setTplDrawerOpen(true)}
                  />
                </div>

                {sending ? (
                  <Button
                    danger
                    shape="circle"
                    onClick={handleStop}
                    icon={<span className="chat-stop-icon" />}
                    className="chat-send-button"
                  />
                ) : (
                  <Button
                    type="primary"
                    shape="circle"
                    onClick={handleSend}
                    icon={<ArrowUpOutlined />}
                    disabled={!input.trim()}
                    className="chat-send-button chat-send-button-primary"
                  />
                )}
              </div>
            </div>
          </div>
          <div className="chat-disclaimer">
            AI 可能会产生错误信息，请核实重要信息。
          </div>
        </div>
          </>
        )}
      </section>

      {/* ---- 抽屉组件保持不变 ---- */}
      {id && (
        <McpResourcesDrawer
          agentId={id}
          open={mcpDrawerOpen}
          onClose={() => setMcpDrawerOpen(false)}
          onUse={(text) => setInput((cur) => cur + text)}
        />
      )}

      <Drawer
        title="📚 选择 Prompt 模板"
        open={tplDrawerOpen}
        onClose={() => setTplDrawerOpen(false)}
        width={520}
      >
        <PromptLibraryPage
          onSelect={(t) => {
            setInput((cur) => (cur ? cur + '\n\n' : '') + t.body);
            setTplDrawerOpen(false);
            msg.success('已插入到输入框');
          }}
        />
      </Drawer>

      <Drawer
        title="⚙️ 模型参数（仅本会话临时生效）"
        open={paramsDrawerOpen}
        onClose={() => setParamsDrawerOpen(false)}
        width={380}
      >
        <div
          style={{
            marginBottom: 16,
            padding: 12,
            background: 'var(--color-warning-soft)',
            borderRadius: 10,
            fontSize: 12,
            color: 'var(--color-warning)',
            border: '1px solid var(--color-border)'
          }}
        >
          💡 这里设置的参数会临时覆盖智能体的默认配置，<b>仅作用于当前浏览器会话</b>，不会写回到智能体本身。<br />
          关闭页面或清空字段即可恢复默认。
        </div>

        <div style={{ marginBottom: 24 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
            <span>🌡 Temperature（创造性）</span>
            <span style={{ fontSize: 12, color: 'var(--color-text-secondary)' }}>
              默认：{agent?.temperature ?? 0.7}
            </span>
          </div>
          <Slider
            min={0}
            max={2}
            step={0.05}
            value={overrides.temperature ?? agent?.temperature ?? 0.7}
            onChange={(v) => setOverrides((o) => ({ ...o, temperature: v as number }))}
            marks={{ 0: '严谨', 0.7: '默认', 1.4: '发散', 2: '混沌' }}
          />
          {overrides.temperature !== undefined && (
            <Button size="small" type="link" style={{ padding: 0 }} onClick={() => setOverrides((o) => ({ ...o, temperature: undefined }))}>
              ↺ 恢复默认
            </Button>
          )}
        </div>

        <div style={{ marginBottom: 24 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
            <span>🎯 Top P（核采样）</span>
            <span style={{ fontSize: 12, color: 'var(--color-text-secondary)' }}>未设置时跟随模型默认</span>
          </div>
          <Slider
            min={0.1}
            max={1}
            step={0.05}
            value={overrides.topP ?? 1}
            onChange={(v) => setOverrides((o) => ({ ...o, topP: v as number }))}
            disabled={overrides.topP === undefined}
          />
          {overrides.topP === undefined ? (
            <Button size="small" type="link" style={{ padding: 0 }} onClick={() => setOverrides((o) => ({ ...o, topP: 1 }))}>
              ✚ 启用 Top P
            </Button>
          ) : (
            <Button size="small" type="link" style={{ padding: 0 }} onClick={() => setOverrides((o) => ({ ...o, topP: undefined }))}>
              ↺ 取消
            </Button>
          )}
        </div>

        <div style={{ marginBottom: 24 }}>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 6 }}>
            <span>📏 Max Tokens（生成长度上限）</span>
            <span style={{ fontSize: 12, color: 'var(--color-text-secondary)' }}>未设置时由模型决定</span>
          </div>
          <InputNumber
            value={overrides.maxTokens}
            onChange={(v) => setOverrides((o) => ({ ...o, maxTokens: v == null ? undefined : Number(v) }))}
            min={1}
            max={16384}
            placeholder="例如 2048"
            style={{ width: '100%' }}
          />
        </div>

        <Button block onClick={() => setOverrides({})}>
          🔄 全部恢复默认
        </Button>
      </Drawer>

      <Drawer
        title="历史对话"
        placement="right"
        width={320}
        onClose={() => setHistoryDrawerOpen(false)}
        open={historyDrawerOpen}
        bodyStyle={{ padding: 0 }}
      >
        {id && (
          <SessionSidebar
            agentId={id}
            activeSessionId={sessionId}
            onChange={(sid, opts) => {
              setSessionId(sid);
              setHighlightId(opts?.highlightMessageId || null);
              setHistoryDrawerOpen(false);
            }}
            refreshTick={sessionRefresh}
          />
        )}
      </Drawer>
    </div>
  );
}

/** 输入栏子组件 */
function MonicaInputBar({
  input, setInput, sending, attachments, imageUrls, uploadingAtt,
  onSend, onStop, onAttach, onRemoveAtt, onRemoveImg, onOpenTpl
}: {
  input: string;
  setInput: (v: string) => void;
  sending: boolean;
  attachments: ChatAttachment[];
  imageUrls: string[];
  uploadingAtt: boolean;
  onSend: () => void;
  onStop: () => void;
  onAttach: (files: File[]) => void;
  onRemoveAtt: (i: number) => void;
  onRemoveImg: (i: number) => void;
  onOpenTpl: () => void;
}) {
  return (
    <div className="monica-input-card">
      {/* 附件/图片预览 */}
      {(attachments.length > 0 || imageUrls.length > 0) && (
        <div className="monica-attach-area">
          {attachments.map((a, i) => (
            <Tag
              key={`att-${i}`}
              color="blue"
              closable
              style={{ borderRadius: 6, padding: '2px 8px', margin: 0 }}
              onClose={() => onRemoveAtt(i)}
            >
              📎 {a.name}
            </Tag>
          ))}
          {imageUrls.map((u, i) => (
            <div key={`img-${i}`} style={{ position: 'relative' }}>
              <AntImage
                src={u}
                width={44}
                height={44}
                style={{ objectFit: 'cover', borderRadius: 8, border: '1px solid #e5e7eb' }}
              />
              <Button
                size="small"
                type="primary"
                shape="circle"
                danger
                style={{
                  position: 'absolute', top: -6, right: -6,
                  width: 18, height: 18, fontSize: 8, padding: 0, minWidth: 18
                }}
                onClick={() => onRemoveImg(i)}
              >
                ✕
              </Button>
            </div>
          ))}
        </div>
      )}

      {/* 输入行 */}
      <div className="monica-input-row">
        <Input.TextArea
          className="monica-input-textarea"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="问我任何问题..."
          autoSize={{ minRows: 1, maxRows: 6 }}
          onPressEnter={(e) => {
            if (!e.shiftKey) {
              e.preventDefault();
              onSend();
            }
          }}
          disabled={sending}
        />
        {sending ? (
          <Button danger shape="circle" onClick={onStop} icon="■" style={{ marginBottom: 4 }} />
        ) : (
          <button
            className="monica-send-btn"
            onClick={onSend}
            disabled={!input.trim()}
            title="发送"
          >
            ↑
          </button>
        )}
      </div>

      {/* 工具栏 */}
      <div className="monica-toolbar">
        <Upload
          multiple
          beforeUpload={(_f, files) => {
            onAttach(files as File[]);
            return false;
          }}
          showUploadList={false}
          accept=".txt,.md,.markdown,.json,.csv,.pdf,.docx,.html,.htm,image/png,image/jpeg,image/webp,image/gif"
        >
          <Button type="text" icon="📎" size="small" style={{ color: '#6b7280' }} loading={uploadingAtt} />
        </Upload>
        <Button type="text" icon="📚" size="small" style={{ color: '#6b7280' }} onClick={onOpenTpl} />
        <div style={{ flex: 1 }} />
      </div>
    </div>
  );
}

function MessageItem({
  message,
  highlighted,
  branch,
  busy,
  onRegenerate,
  onSwitchBranch,
  onCopy
}: {
  message: ChatMessage;
  highlighted?: boolean;
  branch?: BranchInfo;
  busy?: boolean;
  onRegenerate?: (id: string) => void;
  onSwitchBranch?: (userMsgId: string, branchId: string) => void;
  onCopy?: (text: string) => void;
}) {
  const hasBranches = !!branch && branch.total > 1;
  const activeIdx = branch?.activeIndex ?? 0;
  const total = branch?.total ?? 1;

  const goPrev = () => {
    if (!branch || !message.parentId) return;
    const i = Math.max(0, activeIdx - 1);
    onSwitchBranch?.(message.parentId, branch.ids[i]);
  };
  const goNext = () => {
    if (!branch || !message.parentId) return;
    const i = Math.min(total - 1, activeIdx + 1);
    onSwitchBranch?.(message.parentId, branch.ids[i]);
  };

  return (
    <div
      id={'msg-' + message.id}
      style={{
        marginBottom: 12,
        padding: highlighted ? 8 : 0,
        borderRadius: highlighted ? 10 : 0,
        background: highlighted ? 'rgba(254, 243, 199, 0.6)' : 'transparent',
        transition: 'background 0.4s, padding 0.4s'
      }}
    >
      <div className={`bubble ${message.role}`}>
        {message.role === 'assistant' ? (
          <ReactMarkdown>{message.content}</ReactMarkdown>
        ) : message.content.includes('![image](') ? (
          // user 消息含图片：用 markdown 渲染
          <ReactMarkdown>{message.content}</ReactMarkdown>
        ) : (
          message.content
        )}
      </div>
      {message.role === 'assistant' && (
        <div className="monica-msg-actions" style={{ maxWidth: '78%' }}>
          {hasBranches && (
            <Space size={2}>
              <Button size="small" type="text" disabled={activeIdx === 0} onClick={goPrev}>
                ‹
              </Button>
              <span>
                {activeIdx + 1} / {total}
              </span>
              <Button size="small" type="text" disabled={activeIdx === total - 1} onClick={goNext}>
                ›
              </Button>
            </Space>
          )}
          {message.meta?.aborted && <Tag color="orange">已停止</Tag>}
          <Tooltip title="复制">
            <Button size="small" type="text" onClick={() => onCopy?.(message.content)}>
              📋
            </Button>
          </Tooltip>
          <Tooltip title="重新生成（开新分支）">
            <Button
              size="small"
              type="text"
              disabled={busy}
              onClick={() => onRegenerate?.(message.id)}
            >
              🔄
            </Button>
          </Tooltip>
        </div>
      )}
      {message.role === 'assistant' && message.meta && (
        <div style={{ maxWidth: '78%' }}>
          {!!message.meta.reasoning && <ReasoningView reasoning={message.meta.reasoning} />}
          {!!message.meta.retrieved?.length && <RetrievedView retrieved={message.meta.retrieved} />}
          {!!message.meta.toolCalls?.length && <ToolCallView calls={message.meta.toolCalls} />}
        </div>
      )}
    </div>
  );
}

function ReasoningView({ reasoning }: { reasoning: string }) {
  return (
    <Collapse
      size="small"
      ghost
      items={[
        {
          key: 'reasoning',
          label: (
            <span style={{ fontSize: 12, color: 'var(--color-text-secondary)' }}>
              🧠 推理过程
            </span>
          ),
          children: (
            <div style={{ fontSize: 12, color: 'var(--color-text-secondary)', lineHeight: 1.6, maxHeight: 240, overflow: 'auto' }}>
              <ReactMarkdown>{reasoning}</ReactMarkdown>
            </div>
          )
        }
      ]}
    />
  );
}

function RetrievedView({ retrieved, liveStyle }: { retrieved: RetrievedSnippet[]; liveStyle?: boolean }) {
  return (
    <Collapse
      size="small"
      ghost
      defaultActiveKey={liveStyle ? ['rag'] : undefined}
      items={[
        {
          key: 'rag',
          label: (
            <span style={{ fontSize: 12, color: '#6366f1' }}>
              🔍 RAG 命中片段 ({retrieved.length})
            </span>
          ),
          children: (
            <div style={{ fontSize: 12 }}>
              {retrieved.map((r, i) => (
                <div
                  key={i}
                  style={{
                    padding: 8,
                    background: '#f6f8ff',
                    borderRadius: 6,
                    marginBottom: 6,
                    borderLeft: '3px solid #6366f1'
                  }}
                >
                  <div style={{ color: '#6366f1', fontWeight: 600, marginBottom: 4 }}>
                    📄 {r.fileName} · 切片#{r.chunkIndex} · score {r.score.toFixed(3)}
                  </div>
                  <div style={{ color: '#374151', whiteSpace: 'pre-wrap' }}>
                    {r.preview}
                    {r.preview.length >= 200 ? '…' : ''}
                  </div>
                </div>
              ))}
            </div>
          )
        }
      ]}
    />
  );
}

function ToolCallView({ calls, liveStyle }: { calls: ToolCallTrace[]; liveStyle?: boolean }) {
  return (
    <Collapse
      size="small"
      ghost
      defaultActiveKey={liveStyle ? ['tc'] : undefined}
      items={[
        {
          key: 'tc',
          label: (
            <span style={{ fontSize: 12, color: '#f97316' }}>
              🛠 工具调用 ({calls.length})
            </span>
          ),
          children: (
            <div style={{ fontSize: 12 }}>
              {calls.map((t, i) => {
                const isPending = (t.result as any)?.pending;
                const isFailed = t.result?.ok === false;
                return (
                  <div
                    key={i}
                    style={{
                      padding: 8,
                      background: '#fff7ed',
                      borderRadius: 6,
                      marginBottom: 6,
                      borderLeft: '3px solid #f97316'
                    }}
                  >
                    <div style={{ color: '#c2410c', fontWeight: 600, marginBottom: 4 }}>
                      ⚙️ {t.name}{' '}
                      {isPending && <Tag color="processing">运行中…</Tag>}
                      {!isPending && t.result?.durationMs != null && <Tag>{t.result.durationMs}ms</Tag>}
                      {isFailed && <Tag color="error">失败</Tag>}
                    </div>
                    <div style={{ color: '#6b7280' }}>
                      <b>args:</b> {JSON.stringify(t.args)}
                    </div>
                    {!isPending && (
                      <div style={{ color: '#374151', marginTop: 4 }}>
                        <b>result:</b>{' '}
                        <code style={{ wordBreak: 'break-all' }}>
                          {JSON.stringify(t.result?.result ?? t.result?.error ?? t.result).slice(0, 500)}
                        </code>
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
          )
        }
      ]}
    />
  );
}
