import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import {
  DeleteOutlined,
  EditOutlined,
  ArrowRightOutlined,
  LoadingOutlined,
  RobotOutlined,
} from '@ant-design/icons';
import Bubble from '@ant-design/x/es/bubble';
import Prompts from '@ant-design/x/es/prompts';
import Welcome from '@ant-design/x/es/welcome';
import type { ConversationItemType } from '@ant-design/x';
import { useXConversations } from '@ant-design/x-sdk';
import {
  App,
  Checkbox,
  Drawer,
  Grid,
  Input,
} from 'antd';
import type { MenuProps } from 'antd';
import { message } from '../../ui/app-message';
import { useUserStore } from '../../store/user/userStore';
import { usePermissionStore } from '../../store/permission/permissionStore';
import { aiChatApi, conversationStreamUrl } from './api';
import { GongxueAiChatProvider } from './provider';
import { welcomeDescription, workflowPromptExamples } from './welcomeCopy';
import { ImportWizardModal } from '../ImportWizard/ImportWizardModal';
import type { AiSkill } from './types';
import { useAiChatMessageActions } from './useAiChatMessageActions';
import { AiChatComposer, AiChatSidebar } from './AiChatDrawer.parts';
import {
  aiBubbleRoles,
  conversationStatusMeta,
  sortConversations,
  toConversationData,
  type ConversationData,
  type ConversationRunStatus,
} from './AiChatDrawer.helpers';
import './style.css';

export {
  aiBubbleRoles,
  conversationStatusMeta,
  type ConversationData,
  type ConversationRunStatus,
} from './AiChatDrawer.helpers';

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

const AiChatDrawer: React.FC<AiChatDrawerProps> = ({ open, onClose, onRequestingChange }) => {
  const { modal } = App.useApp();
  const user = useUserStore((state) => state.user);
  const permissions = usePermissionStore((state) => state.permissions);
  const screens = Grid.useBreakpoint();
  const isMobile = !screens.sm;
  const [loadingList, setLoadingList] = useState(false);
  // 断点首帧可能尚未解析（isMobile 误判为 true），桌面端默认展开会话侧边栏，
  // 移动端通过 effectiveSidebarOpen 统一隐藏。
  const [sidebarOpen, setSidebarOpen] = useState(true);
  const effectiveSidebarOpen = isMobile ? false : sidebarOpen;
  const [skills, setSkills] = useState<AiSkill[]>([]);
  const [conversationStatus, setConversationStatus] = useState<
    Record<number, ConversationRunStatus>
  >({});
  const [importWizardRunId, setImportWizardRunId] = useState<string | null>(null);
  const [selectionMode, setSelectionMode] = useState(false);
  const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
  const requestAbortRef = useRef(new Map<number, () => void>());
  const providersRef = useRef(new Map<number, GongxueAiChatProvider>());
  const loadedRef = useRef(false);

  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;

  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 {
    input,
    setInput,
    deepThinking,
    setDeepThinking,
    isRequesting,
    messages,
    stopRequest,
    submit,
    customUpload,
    removeAttachment,
    discardPendingAttachments,
    uploadItems,
    promptItems,
    bubbleItems,
  } = useAiChatMessageActions({
    activeConversation,
    activeId,
    provider,
    requestAbortRef,
    markConversationRunning,
    addConversation,
    setActiveConversationKey,
    refreshConversations,
    skills,
    lockedSkill,
    setImportWizardRunId,
  });

  // isRequesting 由 @ant-design/x-sdk 的 useXChat 内部维护且没有完成回调，
  // 这里把它视为外部 SDK 状态做订阅转发，是 Effect 的合理用法。
  useEffect(() => {
    onRequestingChange?.(isRequesting);
  }, [isRequesting, onRequestingChange]);

  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]);

  const switchConversation = useCallback(
    (key: string) => {
      const doSwitch = () => {
        discardPendingAttachments();
        if (isMobile) setSidebarOpen(false);
        setActiveConversationKey(key);
      };
      // 有待发送的附件时先确认，避免静默删除已上传文件
      if (uploadItems.length > 0) {
        modal.confirm({
          title: '切换会话将丢弃未发送的附件',
          content: `当前有 ${uploadItems.length} 个已上传但未发送的附件，切换会话后将被删除，此操作不可恢复。`,
          okText: '切换并丢弃',
          okButtonProps: { danger: true },
          cancelText: '留在当前会话',
          onOk: doSwitch,
        });
        return;
      }
      doSwitch();
    },
    [
      discardPendingAttachments,
      isMobile,
      modal,
      setActiveConversationKey,
      uploadItems.length,
    ],
  );

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

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

  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, modal],
  );

  /** 删除单个会话时中止请求并清理会话运行时状态 */
  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) {
            switchConversation('');
          } else if (conversation.id === activeId) {
            switchConversation(remaining[0].key);
          }
        },
      });
    },
      [
        activeId,
        conversations,
        switchConversation,
        removeConversation,
        removeConversationEntry,
        modal,
      ],
  );

  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([]);
          switchConversation('');
        } 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) {
            switchConversation('');
          } else if (activeId != null && !remaining.some((item) => item.id === activeId)) {
            switchConversation(remaining[0].key);
          }
          if (failedTitles.length) message.error(`删除失败：${failedTitles.join('、')}`);
        }
        setSelectedKeys([]);
        setSelectionMode(false);
      },
    });
  }, [
    activeId,
    conversations,
    removeConversation,
      removeConversationEntry,
      selectedKeys,
      switchConversation,
      setConversations,
      modal,
    ]);

  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 (error) {
        console.error('切换技能失败', error);
        message.error('切换技能失败');
      }
    },
    [activeConversation, setConversation],
  );

  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}
      size={isMobile ? '100%' : 'min(1040px, 92vw)'}
      destroyOnHidden={false}
      className="ai-chat-drawer"
      styles={{ body: { padding: 0, height: '100%' } }}
    >
      <div className="ai-chat-layout">
        <AiChatSidebar
          className={`ai-chat-sidebar${effectiveSidebarOpen ? ' is-open' : ''}`}
          conversationItems={conversationItems}
          activeConversationKey={activeConversationKey}
          selectionMode={selectionMode}
          selectedKeys={selectedKeys}
          loadingList={loadingList}
          conversationCount={conversations.length}
          onActiveChange={(key) => {
            if (selectionMode) toggleConversationSelection(key);
            else switchConversation(key);
          }}
          menu={conversationMenu}
          onStartNewConversation={startNewConversation}
          onSelectAll={selectAllConversations}
          onInvertSelection={invertConversationSelection}
          onDeleteSelected={deleteSelectedConversations}
          onExitSelectionMode={exitSelectionMode}
          onEnterSelectionMode={enterSelectionMode}
        />

        <main className="ai-chat-main">
          <AiChatComposer
            input={input}
            onChange={setInput}
            isRequesting={isRequesting}
            onSubmit={submit}
            onCancel={stopRequest}
            uploadItems={uploadItems}
            onCustomUpload={customUpload}
            onRemoveAttachment={removeAttachment}
            deepThinking={deepThinking}
            onDeepThinkingChange={setDeepThinking}
            lockedSkill={lockedSkill}
            onClearSkill={() => void setLockedSkill(null)}
            onToggleSidebar={() => setSidebarOpen((value) => !value)}
            sidebarOpen={effectiveSidebarOpen}
            skillMenu={skillMenu}
            conversationTitle={activeConversation?.title || 'AI 助手'}
          />

          <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 ||
                    welcomeDescription(user?.roles ?? [], permissions)
                  }
                />
                <Prompts
                  title="你可以这样问"
                  items={[
                    ...promptItems,
                    ...workflowPromptExamples(user?.roles ?? [], permissions).map(
                      (item, index) => ({
                        key: `workflow-${index}`,
                        label: item.label,
                        description: item.description,
                      }),
                    ),
                  ]}
                  wrap
                  onItemClick={({ data }) => submit(String(data.label || ''))}
                />
              </div>
            )}
          </div>

          {importWizardRunId !== null && (
            <ImportWizardModal
              key={importWizardRunId}
              open
              runId={importWizardRunId}
              onClose={() => setImportWizardRunId(null)}
            />
          )}
        </main>
      </div>
    </Drawer>
  );
};

export default AiChatDrawer;
