import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
  CheckSquareOutlined,
  DeleteOutlined,
  EditOutlined,
  ArrowRightOutlined,
  LoadingOutlined,
  MenuFoldOutlined,
  MenuUnfoldOutlined,
  PaperClipOutlined,
  PlusOutlined,
  RobotOutlined,
} from '@ant-design/icons';
import {
  Attachments,
  Bubble,
  Conversations,
  Prompts,
  Sender,
  SenderSwitch,
  Welcome,
} from '@ant-design/x';
import type {
  BubbleItemType,
  BubbleListProps,
  ConversationItemType,
  PromptsItemType,
} from '@ant-design/x';
import type { Attachment } from '@ant-design/x/es/attachments';
import { useXChat, useXConversations, type MessageInfo } from '@ant-design/x-sdk';
import {
  Button,
  Checkbox,
  Drawer,
  Dropdown,
  Grid,
  Input,
  Modal,
  Spin,
  Tooltip,
  Typography,
} from 'antd';
import type { MenuProps, UploadFile, UploadProps } from 'antd';
import { message } from '../../ui/app-message';
import { useSettingsStore } from '../../store/settings/settingsStore';
import { aiChatApi, conversationStreamUrl } from './api';
import { AiMessageContent } from './AiMessageContent';
import { mapHistoryMessage } from './message-mappers';
import { GongxueAiChatProvider } from './provider';
import type {
  AiAttachment,
  AiChatInput,
  AiChatMessage,
  AiChatMessageStatus,
  AiConversation,
  AiFormSchema,
  AiReviewSchema,
  AiReviewSection,
  AiReviewSectionType,
  AiSkill,
  AiSseChunk,
} from './types';
import './style.css';

interface AiChatDrawerProps {
  open: boolean;
  onClose: () => void;
  onRequestingChange?: (working: boolean) => void;
}

interface ConversationData extends AiConversation {
  key: string;
  label: string;
}

export type ConversationRunStatus = 'running' | 'done' | 'error' | 'stopped';

export function conversationStatusMeta(status: ConversationRunStatus): {
  label: string;
  color: string;
} {
  if (status === 'running') return { label: '生成中', color: 'processing' };
  if (status === 'done') return { label: '已完成', color: 'success' };
  if (status === 'error') return { label: '失败', color: 'error' };
  return { label: '已停止', color: 'default' };
}

function sortConversations(items: AiConversation[]): AiConversation[] {
  return [...items].sort((a, b) => {
    const aTime = new Date(a.lastMessageAt || a.updatedAt).getTime();
    const bTime = new Date(b.lastMessageAt || b.updatedAt).getTime();
    return bTime - aTime;
  });
}

function toConversationData(item: AiConversation): ConversationData {
  return { ...item, key: String(item.id), label: item.title };
}

function toUploadFile(attachment: AiAttachment): Attachment<AiAttachment> {
  return {
    uid: String(attachment.id),
    name: attachment.name,
    size: attachment.size,
    status: attachment.status === 'ready' ? 'done' : attachment.status === 'failed' ? 'error' : 'uploading',
    url: attachment.url,
    response: attachment,
    description: attachment.error || undefined,
    cardType: attachment.mimeType.startsWith('image/') ? 'image' : 'file',
  };
}

function emptyAssistant(): AiChatMessage {
  return {
    role: 'assistant',
    content: '',
    reasoningContent: '',
    toolRuns: [],
    attachments: [],
  };
}

export const aiBubbleRoles: BubbleListProps['role'] = {
  user: { placement: 'end', variant: 'filled', shape: 'corner' },
  assistant: { placement: 'start', variant: 'borderless' },
};

const AiChatDrawer: React.FC<AiChatDrawerProps> = ({ open, onClose, onRequestingChange }) => {
  const screens = Grid.useBreakpoint();
  const isMobile = !screens.sm;
  const [loadingList, setLoadingList] = useState(false);
  const [sidebarOpen, setSidebarOpen] = useState(!isMobile);
  const [input, setInput] = useState('');
  const [skills, setSkills] = useState<AiSkill[]>([]);
  const [attachments, setAttachments] = useState<AiAttachment[]>([]);
  const deepThinking = useSettingsStore((state) => state.aiChat.deepThinking);
  const setDeepThinking = useSettingsStore((state) => state.setAiChatDeepThinking);
  const [conversationStatus, setConversationStatus] = useState<
    Record<number, ConversationRunStatus>
  >({});
  const [selectionMode, setSelectionMode] = useState(false);
  const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
  const requestingRef = useRef(false);
  const abortRef = useRef<() => void>(() => undefined);
  const attachmentsRef = useRef<AiAttachment[]>([]);
  const requestAbortRef = useRef(new Map<number, () => void>());
  const providersRef = useRef(new Map<number, GongxueAiChatProvider>());
  const loadedRef = useRef(false);
  const pendingDraftConversationIdRef = useRef<number | null>(null);

  const {
    conversations,
    activeConversationKey,
    setActiveConversationKey,
    addConversation,
    removeConversation,
    setConversation,
    setConversations,
  } = useXConversations({});
  const activeConversationKeyRef = useRef(activeConversationKey);

  const activeConversation = useMemo(
    () => conversations.find((item) => item.key === activeConversationKey) as ConversationData | undefined,
    [activeConversationKey, conversations],
  );
  const activeId = activeConversation?.id ?? null;
  const lockedSkill = skills.find((skill) => skill.key === activeConversation?.lockedSkillKey);
  activeConversationKeyRef.current = activeConversationKey;

  useEffect(() => setSidebarOpen(!isMobile), [isMobile]);

  const refreshConversations = useCallback(async () => {
    const items = sortConversations(await aiChatApi.listConversations()).map(toConversationData);
    setConversations(items);
    const current = activeConversationKeyRef.current;
    setActiveConversationKey(
      current && items.some((item) => item.key === current) ? current : (items[0]?.key ?? ''),
    );
  }, [setActiveConversationKey, setConversations]);

  const markConversationRunning = useCallback((conversationId: number) => {
    setConversationStatus((current) => ({ ...current, [conversationId]: 'running' }));
  }, []);

  const markConversationFinished = useCallback(
    (conversationId: number, result?: { ok: boolean; aborted?: boolean }) => {
      requestAbortRef.current.delete(conversationId);
      setConversationStatus((current) => ({
        ...current,
        [conversationId]: result?.ok ? 'done' : result?.aborted ? 'stopped' : 'error',
      }));
    },
    [],
  );

  const provider = useMemo(
    () => {
      if (!activeId) return undefined;
      const existing = providersRef.current.get(activeId);
      if (existing) return existing;
      const created = new GongxueAiChatProvider(conversationStreamUrl(activeId), (result) => {
        void refreshConversations();
        markConversationFinished(activeId, result);
      });
      providersRef.current.set(activeId, created);
      return created;
    },
    [activeId, markConversationFinished, refreshConversations],
  );

  const { messages, onRequest, onReload, isRequesting, abort, setMessage, queueRequest } = useXChat<
    AiChatMessage,
    AiChatMessage,
    AiChatInput,
    AiSseChunk
  >({
    provider,
    conversationKey: activeConversationKey || 'no-conversation',
    defaultMessages: async () => {
      if (!activeId) return [];
      const page = await aiChatApi.listMessages(activeId);
      return page.items.map(mapHistoryMessage);
    },
    requestPlaceholder: emptyAssistant(),
    requestFallback: (
      params: Partial<AiChatInput>,
      { error, messageInfo }: { error: Error; messageInfo: MessageInfo<AiChatMessage> },
    ) => ({
      ...(params.reloadMessage || messageInfo?.message || emptyAssistant()),
      error: error.name === 'AbortError' ? undefined : '连接中断，请稍后重试',
      cancelled: error.name === 'AbortError',
    }),
  });

  useEffect(() => {
    if (!provider) return;
    provider.onExternalReview = (messageId, review) => {
      setMessage(messageId, (info) => ({
        message: {
          ...info.message,
          reviews: (info.message.reviews ?? []).some((item) => item.id === review.id)
            ? (info.message.reviews ?? []).map((item) => (item.id === review.id ? review : item))
            : [...(info.message.reviews ?? []), review],
        },
      }));
    };
  }, [provider, setMessage]);

  requestingRef.current = isRequesting;
  abortRef.current = abort;
  attachmentsRef.current = attachments;

  useEffect(() => {
    onRequestingChange?.(isRequesting);
  }, [isRequesting, onRequestingChange]);

  const stopRequest = useCallback(() => {
    if (requestingRef.current) abortRef.current();
  }, []);

  const requestWithStatus = useCallback(
    (params: AiChatInput) => {
      if (!activeId || !provider) return;
      requestAbortRef.current.set(activeId, () => provider.request.abort());
      markConversationRunning(activeId);
      onRequest(params);
    },
    [activeId, markConversationRunning, onRequest, provider],
  );

  const reloadWithStatus = useCallback(
    (messageInfo: MessageInfo<AiChatMessage>) => {
      if (!activeId || !provider || typeof messageInfo.message.id !== 'number') return;
      requestAbortRef.current.set(activeId, () => provider.request.abort());
      markConversationRunning(activeId);
      onReload(messageInfo.id, {
        message: '',
        attachmentIds: [],
        skillKey: activeConversation?.lockedSkillKey ?? null,
        clientRequestId: crypto.randomUUID(),
        reasoningEffort: deepThinking ? 'high' : null,
        regenerateMessageId: messageInfo.message.id,
        reloadMessage: messageInfo.message,
      });
    },
    [
      activeConversation?.lockedSkillKey,
      activeId,
      deepThinking,
      markConversationRunning,
      onReload,
      provider,
    ],
  );

  const discardPendingAttachments = useCallback(() => {
    const pending = attachmentsRef.current;
    attachmentsRef.current = [];
    setAttachments([]);
    for (const attachment of pending) {
      void aiChatApi.deleteAttachment(attachment.id).catch(() => undefined);
    }
  }, []);

  useEffect(() => {
    if (!open || loadedRef.current) return;
    let cancelled = false;
    setLoadingList(true);
    Promise.all([aiChatApi.listSkills(), aiChatApi.listConversations()])
      .then(([skillItems, conversationItems]) => {
        if (cancelled) return;
        loadedRef.current = true;
        setSkills(skillItems);
        const data = sortConversations(conversationItems).map(toConversationData);
        setConversations(data);
        setActiveConversationKey(data[0]?.key ?? '');
      })
      .catch(() => message.error('加载 AI 助手失败'))
      .finally(() => !cancelled && setLoadingList(false));
    return () => {
      cancelled = true;
    };
  }, [open, setActiveConversationKey, setConversations]);

  useEffect(() => {
    discardPendingAttachments();
    if (isMobile) setSidebarOpen(false);
  }, [activeConversationKey, discardPendingAttachments, isMobile]);

  useEffect(
    () => () => {
      for (const abort of requestAbortRef.current.values()) abort();
      requestAbortRef.current.clear();
      providersRef.current.clear();
    },
    [],
  );

  /** 新建对话（Codex 风格）：先进入草稿态，发送第一条消息时才创建 session */
  const startNewConversation = useCallback(() => {
    setActiveConversationKey('');
    if (isMobile) setSidebarOpen(false);
  }, [isMobile, setActiveConversationKey]);

  const renameConversation = useCallback(
    (conversation: ConversationData) => {
      let title = conversation.title;
      Modal.confirm({
        title: '重命名会话',
        icon: <EditOutlined />,
        content: <Input defaultValue={title} maxLength={100} onChange={(event) => (title = event.target.value)} />,
        okText: '保存',
        cancelText: '取消',
        onOk: async () => {
          const normalized = title.trim();
          if (!normalized) throw new Error('请输入会话名称');
          const updated = toConversationData(
            await aiChatApi.updateConversation(conversation.id, { title: normalized }),
          );
          setConversation(conversation.key, updated);
        },
      });
    },
    [setConversation],
  );

  /** 删除单个会话时中止请求并清理会话运行时状态 */
  const removeConversationEntry = useCallback(
    (conversation: ConversationData) => {
      const abortRequest = requestAbortRef.current.get(conversation.id);
      if (abortRequest) abortRequest();
      else if (conversation.id === activeId) stopRequest();
      requestAbortRef.current.delete(conversation.id);
      providersRef.current.delete(conversation.id);
      setConversationStatus((current) => {
        const next = { ...current };
        delete next[conversation.id];
        return next;
      });
    },
    [activeId, setConversationStatus, stopRequest],
  );

  const deleteConversation = useCallback(
    (conversation: ConversationData) => {
      Modal.confirm({
        title: '删除会话',
        content: '该会话及全部历史消息将被永久删除。',
        okText: '删除',
        okButtonProps: { danger: true },
        cancelText: '取消',
        onOk: async () => {
          await aiChatApi.deleteConversation(conversation.id);
          removeConversationEntry(conversation);
          removeConversation(conversation.key);
          const remaining = conversations.filter((item) => item.key !== conversation.key);
          if (!remaining.length) {
            setActiveConversationKey('');
          } else if (conversation.id === activeId) {
            setActiveConversationKey(remaining[0].key);
          }
        },
      });
    },
    [
      activeId,
      conversations,
      removeConversation,
      removeConversationEntry,
      setActiveConversationKey,
    ],
  );

  const enterSelectionMode = useCallback(() => {
    setSelectedKeys([]);
    setSelectionMode(true);
  }, []);

  const exitSelectionMode = useCallback(() => {
    setSelectedKeys([]);
    setSelectionMode(false);
  }, []);

  const selectAllConversations = useCallback(() => {
    setSelectedKeys(conversations.map((item) => item.key));
  }, [conversations]);

  const invertConversationSelection = useCallback(() => {
    setSelectedKeys((current) => {
      const selected = new Set(current);
      return conversations.map((item) => item.key).filter((key) => !selected.has(key));
    });
  }, [conversations]);

  const toggleConversationSelection = useCallback((key: string) => {
    setSelectedKeys((current) =>
      current.includes(key) ? current.filter((item) => item !== key) : [...current, key],
    );
  }, []);

  const deleteSelectedConversations = useCallback(() => {
    const selected = conversations.filter((item) =>
      selectedKeys.includes(item.key),
    ) as ConversationData[];
    if (!selected.length) return;
    Modal.confirm({
      title: `删除选中的 ${selected.length} 个会话`,
      content: '选中的会话及全部历史消息将被永久删除，此操作不可恢复。',
      okText: '删除',
      okButtonProps: { danger: true },
      cancelText: '取消',
      onOk: async () => {
        if (selected.length === conversations.length) {
          for (const abort of requestAbortRef.current.values()) abort();
          requestAbortRef.current.clear();
          providersRef.current.clear();
          setConversationStatus({});
          await aiChatApi.deleteAllConversations();
          setConversations([]);
          setActiveConversationKey('');
        } else {
          for (const item of selected) removeConversationEntry(item);
          const deletedKeys: string[] = [];
          const failedTitles: string[] = [];
          await Promise.all(
            selected.map(async (item) => {
              try {
                await aiChatApi.deleteConversation(item.id);
                removeConversation(item.key);
                deletedKeys.push(item.key);
              } catch {
                failedTitles.push(item.title);
              }
            }),
          );
          const deleted = new Set(deletedKeys);
          const remaining = conversations.filter((item) => !deleted.has(item.key));
          setConversations(remaining);
          if (!remaining.length) {
            setActiveConversationKey('');
          } else if (activeId != null && !remaining.some((item) => item.id === activeId)) {
            setActiveConversationKey(remaining[0].key);
          }
          if (failedTitles.length) message.error(`删除失败：${failedTitles.join('、')}`);
        }
        setSelectedKeys([]);
        setSelectionMode(false);
      },
    });
  }, [
    activeId,
    conversations,
    removeConversation,
    removeConversationEntry,
    selectedKeys,
    setActiveConversationKey,
    setConversations,
  ]);

  const conversationMenu = useCallback(
    (item: ConversationItemType): MenuProps => ({
      items: [
        { key: 'rename', label: '重命名', icon: <EditOutlined /> },
        { key: 'delete', label: '删除', icon: <DeleteOutlined />, danger: true },
      ],
      onClick: ({ key, domEvent }) => {
        domEvent.stopPropagation();
        const conversation = conversations.find((entry) => entry.key === item.key) as ConversationData;
        if (key === 'rename') renameConversation(conversation);
        if (key === 'delete') deleteConversation(conversation);
      },
    }),
    [conversations, deleteConversation, renameConversation],
  );

  const setLockedSkill = useCallback(
    async (skillKey: string | null) => {
      if (!activeConversation) return;
      try {
        const updated = toConversationData(
          await aiChatApi.updateConversation(activeConversation.id, { lockedSkillKey: skillKey }),
        );
        setConversation(activeConversation.key, updated);
      } catch {
        message.error('切换技能失败');
      }
    },
    [activeConversation, setConversation],
  );

  const submit = useCallback(
    (value: string) => {
      const text = value.trim();
      if (!text || isRequesting) return;
      const submittedAttachments = attachmentsRef.current;
      attachmentsRef.current = [];
      setAttachments([]);
      setInput('');
      const params: AiChatInput = {
        message: text,
        attachmentIds: submittedAttachments.map((item) => item.id),
        skillKey: activeConversation?.lockedSkillKey ?? null,
        clientRequestId: crypto.randomUUID(),
        reasoningEffort: deepThinking ? 'high' : null,
        localAttachments: submittedAttachments,
      };
      if (activeId != null) {
        requestWithStatus(params);
        return;
      }
      // 草稿态：先创建 session，再发送第一条消息
      void (async () => {
        try {
          const created = toConversationData(await aiChatApi.createConversation());
          addConversation(created, 'prepend');
          pendingDraftConversationIdRef.current = created.id;
          markConversationRunning(created.id);
          // 通过 XChat 的队列机制发送：等会话 key 切换并加载完成后再真正发出，
          // 保证消息写入新会话的 store，界面能正常显示对话内容。
          queueRequest(created.key, params);
          setActiveConversationKey(created.key);
        } catch {
          message.error('创建会话失败，请重试');
          attachmentsRef.current = submittedAttachments;
          setAttachments(submittedAttachments);
          setInput(text);
        }
      })();
    },
    [
      activeConversation?.lockedSkillKey,
      activeId,
      addConversation,
      deepThinking,
      isRequesting,
      markConversationRunning,
      queueRequest,
      requestWithStatus,
      setActiveConversationKey,
    ],
  );

  // 草稿 session 创建完成、provider 就绪后注册中止句柄
  useEffect(() => {
    if (activeId == null || !provider) return;
    if (activeId !== pendingDraftConversationIdRef.current) return;
    pendingDraftConversationIdRef.current = null;
    requestAbortRef.current.set(activeId, () => provider.request.abort());
  }, [activeId, provider]);

  const reloadMessage = useCallback(
    (messageInfo: MessageInfo<AiChatMessage>) => {
      reloadWithStatus(messageInfo);
    },
    [reloadWithStatus],
  );

  const submitForm = useCallback(
    (form: AiFormSchema, values: Record<string, unknown>) => {
      if (!activeId || isRequesting) return;
      requestWithStatus({
        message: '表单提交',
        attachmentIds: [],
        skillKey: activeConversation?.lockedSkillKey ?? null,
        clientRequestId: crypto.randomUUID(),
        reasoningEffort: deepThinking ? 'high' : null,
        formSubmission: { formId: form.id, values, formTitle: form.title },
      });
    },
    [activeConversation?.lockedSkillKey, activeId, deepThinking, isRequesting, requestWithStatus],
  );

  const submitReview = useCallback(
    (reviewId: string, reviewTitle?: string) => {
      if (!activeId || isRequesting) return;
      requestWithStatus({
        message: '确认批量导入',
        attachmentIds: [],
        skillKey: activeConversation?.lockedSkillKey ?? null,
        clientRequestId: crypto.randomUUID(),
        reasoningEffort: deepThinking ? 'high' : null,
        reviewSubmission: { reviewId, reviewTitle },
      });
    },
    [activeConversation?.lockedSkillKey, activeId, deepThinking, isRequesting, requestWithStatus],
  );

  const confirmReviewStep = useCallback(
    async (
      messageId: number | undefined,
      reviewId: string,
      sectionKey: AiReviewSection['key'],
    ): Promise<AiReviewSchema> => {
      const updated = await aiChatApi.confirmReviewStep(reviewId, sectionKey);
      const apply = (review: AiReviewSchema) => {
        if (provider?.onExternalReview && typeof messageId === 'number') {
          provider.onExternalReview(messageId, review);
        } else if (typeof messageId === 'number') {
          setMessage(messageId, (info) => {
            const reviews = info.message.reviews ?? [];
            const exists = reviews.some((item) => item.id === review.id);
            return {
              message: {
                ...info.message,
                reviews: exists
                  ? reviews.map((item) => (item.id === review.id ? review : item))
                  : [...reviews, review],
              },
            };
          });
        }
      };
      apply(updated);
      return updated;
    },
    [provider, setMessage],
  );

  const confirmReviewGroup = useCallback(
    async (
      messageId: number | undefined,
      reviewId: string,
      type: AiReviewSectionType,
    ): Promise<AiReviewSchema> => {
      const updated = await aiChatApi.confirmReviewGroup(reviewId, type);
      if (provider?.onExternalReview && typeof messageId === 'number') {
        provider.onExternalReview(messageId, updated);
      } else if (typeof messageId === 'number') {
        setMessage(messageId, (info) => {
          const reviews = info.message.reviews ?? [];
          const exists = reviews.some((item) => item.id === updated.id);
          return {
            message: {
              ...info.message,
              reviews: exists
                ? reviews.map((item) => (item.id === updated.id ? updated : item))
                : [...reviews, updated],
            },
          };
        });
      }
      return updated;
    },
    [provider, setMessage],
  );

  const updateFeedback = useCallback(
    async (messageInfo: MessageInfo<AiChatMessage>, feedback: 'like' | 'dislike' | null) => {
      if (typeof messageInfo.message.id !== 'number') return;
      try {
        await aiChatApi.setFeedback(messageInfo.message.id, feedback);
        setMessage(messageInfo.id, {
          message: { ...messageInfo.message, feedback },
        });
      } catch {
        message.error('提交反馈失败');
      }
    },
    [setMessage],
  );

  const customUpload = useCallback<NonNullable<UploadProps['customRequest']>>(async (options) => {
    const file = options.file as File;
    if (attachmentsRef.current.length >= 5) {
      const error = new Error('每条消息最多添加 5 个附件');
      options.onError?.(error);
      message.warning(error.message);
      return;
    }
    try {
      const uploaded = await aiChatApi.uploadAttachment(file);
      setAttachments((items) => [...items, uploaded]);
      options.onSuccess?.(uploaded, file);
    } catch (error) {
      options.onError?.(error instanceof Error ? error : new Error('附件上传失败'));
      message.error('附件上传失败');
    }
  }, []);

  const removeAttachment = useCallback(async (file: UploadFile<AiAttachment>) => {
    const attachment = file.response;
    if (!attachment) return true;
    try {
      await aiChatApi.deleteAttachment(attachment.id);
      setAttachments((items) => items.filter((item) => item.id !== attachment.id));
      return true;
    } catch {
      message.error('删除附件失败');
      return false;
    }
  }, []);

  const uploadItems = useMemo(() => attachments.map(toUploadFile), [attachments]);
  const promptItems = useMemo<PromptsItemType[]>(
    () =>
      (lockedSkill ? [lockedSkill] : skills)
        .flatMap((skill) => skill.examples.slice(0, lockedSkill ? 4 : 1).map((example) => ({ skill, example })))
        .slice(0, 5)
        .map(({ skill, example }) => ({
          key: `${skill.key}-${example}`,
          label: example,
          description: skill.name,
        })),
    [lockedSkill, skills],
  );

  const bubbleItems = useMemo<BubbleItemType[]>(
    () =>
      messages.map((info) => ({
        key: info.id,
        role: info.message.role === 'assistant' ? 'assistant' : 'user',
        status: info.status,
        content: info.message,
        contentRender: (content: AiChatMessage) => (
          <AiMessageContent
            message={content}
            status={info.status as AiChatMessageStatus}
            onReload={content.role === 'assistant' && info.status !== 'loading' ? () => reloadMessage(info) : undefined}
            onFeedback={content.role === 'assistant' ? (feedback) => void updateFeedback(info, feedback) : undefined}
            onSubmitForm={submitForm}
            onSubmitReview={submitReview}
            onConfirmReviewStep={confirmReviewStep}
            onConfirmReviewGroup={confirmReviewGroup}
          />
        ),
      })),
    [confirmReviewGroup, confirmReviewStep, messages, reloadMessage, submitForm, submitReview, updateFeedback],
  );

  const conversationItems = useMemo<ConversationItemType[]>(
    () =>
      conversations.map((item) => {
        const status = conversationStatus[item.id];
        let statusIndicator: React.ReactNode = null;
        if (status === 'running') {
          statusIndicator = (
            <LoadingOutlined
              spin
              className="ai-chat-conversation-loading"
              aria-label="生成中"
              role="status"
            />
          );
        } else if (status === 'error' || status === 'stopped') {
          statusIndicator = (
            <span
              className={`ai-chat-conversation-state is-${status}`}
              aria-label={conversationStatusMeta(status).label}
            >
              <i />
              {conversationStatusMeta(status).label}
            </span>
          );
        }
        const label = (
          <span className="ai-chat-conversation-label">
            {selectionMode && (
              <Checkbox
                checked={selectedKeys.includes(item.key)}
                className="ai-chat-conversation-check"
                aria-label={`选择 ${item.title}`}
              />
            )}
            <span className="ai-chat-conversation-label__title">{item.title}</span>
            {statusIndicator}
          </span>
        );
        return { ...item, label };
      }),
    [conversationStatus, conversations, selectedKeys, selectionMode],
  );

  const skillMenu: MenuProps = {
    items: [
      { key: 'auto', label: '自动选择技能' },
      { type: 'divider' },
      ...skills.map((skill) => ({ key: skill.key, label: skill.name })),
    ],
    selectedKeys: [activeConversation?.lockedSkillKey || 'auto'],
    onClick: ({ key }) => void setLockedSkill(key === 'auto' ? null : key),
  };

  return (
    <Drawer
      title={<span className="ai-chat-title"><RobotOutlined />恭学 AI 助手</span>}
      open={open}
      closeIcon={<ArrowRightOutlined title="收起到后台继续运行" />}
      onClose={onClose}
      width={isMobile ? '100%' : 'min(1040px, 92vw)'}
      destroyOnHidden={false}
      className="ai-chat-drawer"
      styles={{ body: { padding: 0, height: '100%' } }}
    >
      <div className="ai-chat-layout">
        <aside className={`ai-chat-sidebar${sidebarOpen ? ' is-open' : ''}`}>
          <Conversations
            items={conversationItems}
            activeKey={activeConversationKey}
            onActiveChange={(key) => {
              if (selectionMode) toggleConversationSelection(key);
              else setActiveConversationKey(key);
            }}
            menu={selectionMode ? undefined : conversationMenu}
            creation={
              selectionMode
                ? undefined
                : { label: '新对话', icon: <PlusOutlined />, onClick: startNewConversation }
            }
          />
          {loadingList && <Spin className="ai-chat-sidebar__loading" />}
          <div className="ai-chat-sidebar__footer">
            {selectionMode ? (
              <>
                <span className="ai-chat-sidebar__selected-count">{selectedKeys.length} 已选</span>
                <Button size="small" type="text" onClick={selectAllConversations}>
                  全选
                </Button>
                <Button size="small" type="text" onClick={invertConversationSelection}>
                  反选
                </Button>
                <Button
                  size="small"
                  type="text"
                  danger
                  disabled={selectedKeys.length === 0}
                  onClick={deleteSelectedConversations}
                >
                  删除
                </Button>
                <Button size="small" type="text" onClick={exitSelectionMode}>
                  取消
                </Button>
              </>
            ) : (
              <Button
                size="small"
                type="text"
                icon={<CheckSquareOutlined />}
                disabled={conversations.length === 0}
                onClick={enterSelectionMode}
              >
                管理
              </Button>
            )}
          </div>
        </aside>

        <main className="ai-chat-main">
          <div className="ai-chat-toolbar">
            <Tooltip title={sidebarOpen ? '收起会话' : '展开会话'}>
              <Button
                type="text"
                icon={sidebarOpen ? <MenuFoldOutlined /> : <MenuUnfoldOutlined />}
                onClick={() => setSidebarOpen((value) => !value)}
              />
            </Tooltip>
            <Typography.Text ellipsis>{activeConversation?.title || 'AI 助手'}</Typography.Text>
            <Dropdown menu={skillMenu} trigger={['click']}>
              <Button size="small">{lockedSkill?.name || '自动技能'}</Button>
            </Dropdown>
          </div>

          <div className="ai-chat-messages">
            {messages.length ? (
              <Bubble.List items={bubbleItems} role={aiBubbleRoles} autoScroll />
            ) : (
              <div className="ai-chat-welcome">
                <Welcome
                  variant="borderless"
                  icon={<RobotOutlined />}
                  title="你好，我是恭学 AI 助手"
                  description={lockedSkill?.description || '我会在你的权限范围内查询数据，也能通过表单帮你录入学生等业务信息。'}
                />
                <Prompts
                  title="你可以这样问"
                  items={promptItems}
                  wrap
                  onItemClick={({ data }) => submit(String(data.label || ''))}
                />
              </div>
            )}
          </div>

          <div className="ai-chat-composer">
            <Sender
              value={input}
              onChange={setInput}
              loading={isRequesting}
              onSubmit={submit}
              onCancel={stopRequest}
              autoSize={{ minRows: 1, maxRows: 6 }}
              placeholder="询问学生、考勤、宿舍或账单数据"
              skill={
                lockedSkill
                  ? {
                      title: lockedSkill.name,
                      value: lockedSkill.key,
                      closable: { onClose: () => void setLockedSkill(null) },
                    }
                  : undefined
              }
              header={
                uploadItems.length > 0 && (
                  <div className="ai-chat-sender-header">
                    <Attachments
                      items={uploadItems}
                      customRequest={customUpload}
                      onRemove={removeAttachment}
                      accept="image/jpeg,image/png,image/webp,application/pdf,.docx,.xlsx"
                      multiple
                    />
                  </div>
                )
              }
              footer={
                <div className="ai-chat-sender-footer">
                  <Tooltip title="添加附件">
                    <Attachments
                      items={[]}
                      customRequest={customUpload}
                      onRemove={removeAttachment}
                      accept="image/jpeg,image/png,image/webp,application/pdf,.docx,.xlsx"
                      multiple
                      placeholder={{ title: '添加附件', description: '图片、PDF、Word、Excel，单个不超过 10MB' }}
                    >
                      <Button type="text" icon={<PaperClipOutlined />} aria-label="添加附件" />
                    </Attachments>
                  </Tooltip>
                  <SenderSwitch
                    checkedChildren="深度思考"
                    unCheckedChildren="普通"
                    value={deepThinking}
                    onChange={setDeepThinking}
                    disabled={isRequesting}
                  />
                </div>
              }
            />
            <Typography.Text type="secondary" className="ai-chat-disclaimer">
              AI 操作均在权限范围内执行，写操作需通过表单确认，重要信息请以系统记录为准
            </Typography.Text>
          </div>
        </main>
      </div>
    </Drawer>
  );
};

export default AiChatDrawer;
