import React, {
  useCallback,
  useRef,
  useState,
  useMemo,
  forwardRef,
  useImperativeHandle,
  ForwardedRef,
  useEffect
} from 'react';
import Script from 'next/script';
import { throttle } from 'lodash';
import type { ExportChatType } from '@/types/chat.d';
import type { ChatItemType, ChatSiteItemType } from '@fastgpt/global/core/chat/type.d';
import type { ChatHistoryItemResType } from '@fastgpt/global/core/chat/type.d';
import { useToast } from '@/web/common/hooks/useToast';
import { useAudioPlay } from '@/web/common/utils/voice';
import { getErrText } from '@fastgpt/global/common/error/utils';
import { useCopyData } from '@/web/common/hooks/useCopyData';
import {
  Box,
  Card,
  Flex,
  Input,
  Button,
  useTheme,
  BoxProps,
  FlexProps,
  Image,
  Textarea,
  Checkbox
} from '@chakra-ui/react';
import { feConfigs } from '@/web/common/system/staticData';
import { EventNameEnum, eventBus } from '@/web/common/utils/eventbus';
import { adaptChat2GptMessages } from '@fastgpt/global/core/chat/adapt';
import { useMarkdown } from '@/web/common/hooks/useMarkdown';
import { ModuleItemType } from '@fastgpt/global/core/module/type.d';
import { VariableInputEnum } from '@fastgpt/global/core/module/constants';
import { useForm } from 'react-hook-form';
import type { ChatMessageItemType } from '@fastgpt/global/core/ai/type.d';
import { fileDownload } from '@/web/common/file/utils';
import { htmlTemplate } from '@/constants/common';
import { useRouter } from 'next/router';
import { useSystemStore } from '@/web/common/system/useSystemStore';
import { useTranslation } from 'next-i18next';
import { customAlphabet } from 'nanoid';
import {
  closeCustomFeedback,
  updateChatAdminFeedback,
  updateChatUserFeedback
} from '@/web/core/chat/api';
import type { AdminMarkType } from './SelectMarkCollection';

import MyIcon from '@/components/Icon';
import Avatar from '@/components/Avatar';
import Markdown, { CodeClassName } from '@/components/Markdown';
import MySelect from '@/components/Select';
import MyTooltip from '../MyTooltip';
import dynamic from 'next/dynamic';
const ResponseTags = dynamic(() => import('./ResponseTags'));
const FeedbackModal = dynamic(() => import('./FeedbackModal'));
const ReadFeedbackModal = dynamic(() => import('./ReadFeedbackModal'));
const SelectMarkCollection = dynamic(() => import('./SelectMarkCollection'));

import styles from './index.module.scss';
import { postQuestionGuide } from '@/web/core/ai/api';
import { splitGuideModule } from '@fastgpt/global/core/module/utils';
import type { AppTTSConfigType } from '@fastgpt/global/core/module/type.d';
import MessageInput from './MessageInput';
import { ModuleOutputKeyEnum } from '@fastgpt/global/core/module/constants';
import ChatBoxDivider from '../core/chat/Divider';

const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz1234567890', 24);

const textareaMinH = '22px';

type generatingMessageProps = { text?: string; name?: string; status?: 'running' | 'finish' };

export type StartChatFnProps = {
  chatList: ChatSiteItemType[];
  messages: ChatMessageItemType[];
  controller: AbortController;
  variables: Record<string, any>;
  generatingMessage: (e: generatingMessageProps) => void;
};

export type ComponentRef = {
  getChatHistories: () => ChatSiteItemType[];
  resetVariables: (data?: Record<string, any>) => void;
  resetHistory: (history: ChatSiteItemType[]) => void;
  scrollToBottom: (behavior?: 'smooth' | 'auto') => void;
  sendPrompt: (question: string) => void;
};

enum FeedbackTypeEnum {
  user = 'user',
  admin = 'admin',
  hidden = 'hidden'
}

type Props = {
  feedbackType?: `${FeedbackTypeEnum}`;
  showMarkIcon?: boolean; // admin mark dataset
  showVoiceIcon?: boolean;
  showEmptyIntro?: boolean;
  appAvatar?: string;
  userAvatar?: string;
  userGuideModule?: ModuleItemType;
  showFileSelector?: boolean;
  active?: boolean; // can use

  // not chat test params
  appId?: string;
  chatId?: string;
  shareId?: string;
  outLinkUid?: string;

  onUpdateVariable?: (e: Record<string, any>) => void;
  onStartChat?: (e: StartChatFnProps) => Promise<{
    responseText: string;
    [ModuleOutputKeyEnum.responseData]: ChatHistoryItemResType[];
    isNewChat?: boolean;
  }>;
  onDelMessage?: (e: { contentId?: string; index: number }) => void;
};

const ChatBox = (
  {
    feedbackType = FeedbackTypeEnum.hidden,
    showMarkIcon = false,
    showVoiceIcon = true,
    showEmptyIntro = false,
    appAvatar,
    userAvatar,
    userGuideModule,
    showFileSelector,
    active = true,
    appId,
    chatId,
    shareId,
    outLinkUid,
    onUpdateVariable,
    onStartChat,
    onDelMessage
  }: Props,
  ref: ForwardedRef<ComponentRef>
) => {
  const ChatBoxRef = useRef<HTMLDivElement>(null);
  const theme = useTheme();
  const router = useRouter();
  const { t } = useTranslation();
  const { toast } = useToast();
  const { isPc, setLoading } = useSystemStore();
  const TextareaDom = useRef<HTMLTextAreaElement>(null);
  const chatController = useRef(new AbortController());
  const questionGuideController = useRef(new AbortController());
  const isNewChatReplace = useRef(false);

  const [refresh, setRefresh] = useState(false);
  const [variables, setVariables] = useState<Record<string, any>>({}); // settings variable
  const [chatHistory, setChatHistory] = useState<ChatSiteItemType[]>([]);
  const [feedbackId, setFeedbackId] = useState<string>();
  const [readFeedbackData, setReadFeedbackData] = useState<{
    chatItemId: string;
    content: string;
  }>();
  const [adminMarkData, setAdminMarkData] = useState<AdminMarkType & { chatItemId: string }>();
  const [questionGuides, setQuestionGuide] = useState<string[]>([]);

  const isChatting = useMemo(
    () =>
      chatHistory[chatHistory.length - 1] &&
      chatHistory[chatHistory.length - 1]?.status !== 'finish',
    [chatHistory]
  );

  const { welcomeText, variableModules, questionGuide, ttsConfig } = useMemo(
    () => splitGuideModule(userGuideModule),
    [userGuideModule]
  );

  // compute variable input is finish.
  const [variableInputFinish, setVariableInputFinish] = useState(false);
  const variableIsFinish = useMemo(() => {
    if (!variableModules || variableModules.length === 0 || chatHistory.length > 0) return true;

    for (let i = 0; i < variableModules.length; i++) {
      const item = variableModules[i];
      if (item.required && !variables[item.key]) {
        return false;
      }
    }

    return variableInputFinish;
  }, [chatHistory.length, variableInputFinish, variableModules, variables]);

  const { register, reset, getValues, setValue, handleSubmit } = useForm<Record<string, any>>({
    defaultValues: variables
  });

  // 滚动到底部
  const scrollToBottom = useCallback(
    (behavior: 'smooth' | 'auto' = 'smooth') => {
      if (!ChatBoxRef.current) return;
      ChatBoxRef.current.scrollTo({
        top: ChatBoxRef.current.scrollHeight,
        behavior
      });
    },
    [ChatBoxRef]
  );
  // 聊天信息生成中……获取当前滚动条位置，判断是否需要滚动到底部
  const generatingScroll = useCallback(
    throttle(() => {
      if (!ChatBoxRef.current) return;
      const isBottom =
        ChatBoxRef.current.scrollTop + ChatBoxRef.current.clientHeight + 150 >=
        ChatBoxRef.current.scrollHeight;

      isBottom && scrollToBottom('auto');
    }, 100),
    []
  );
  // eslint-disable-next-line react-hooks/exhaustive-deps
  const generatingMessage = ({ text = '', status, name }: generatingMessageProps) => {
    setChatHistory((state) =>
      state.map((item, index) => {
        if (index !== state.length - 1) return item;
        return {
          ...item,
          ...(text
            ? {
                value: item.value + text
              }
            : {}),
          ...(status && name
            ? {
                status,
                moduleName: name
              }
            : {})
        };
      })
    );
    generatingScroll();
  };

  // 重置输入内容
  const resetInputVal = useCallback((val: string) => {
    if (!TextareaDom.current) return;

    setTimeout(() => {
      /* 回到最小高度 */
      if (TextareaDom.current) {
        TextareaDom.current.value = val;
        TextareaDom.current.style.height =
          val === '' ? textareaMinH : `${TextareaDom.current.scrollHeight}px`;
      }
      setRefresh((state) => !state);
    }, 100);
  }, []);

  // create question guide
  const createQuestionGuide = useCallback(
    async ({ history }: { history: ChatSiteItemType[] }) => {
      if (!questionGuide || chatController.current?.signal?.aborted) return;

      try {
        const abortSignal = new AbortController();
        questionGuideController.current = abortSignal;

        const result = await postQuestionGuide(
          {
            messages: adaptChat2GptMessages({ messages: history, reserveId: false }).slice(-6),
            shareId
          },
          abortSignal
        );
        if (Array.isArray(result)) {
          setQuestionGuide(result);
          setTimeout(() => {
            scrollToBottom();
          }, 100);
        }
      } catch (error) {}
    },
    [questionGuide, scrollToBottom, shareId]
  );

  /**
   * user confirm send prompt
   */
  const sendPrompt = useCallback(
    async (variables: Record<string, any> = {}, inputVal = '', history = chatHistory) => {
      if (!onStartChat) return;
      if (isChatting) {
        toast({
          title: '正在聊天中...请等待结束',
          status: 'warning'
        });
        return;
      }
      questionGuideController.current?.abort('stop');
      // get input value
      const val = inputVal.trim();

      if (!val) {
        toast({
          title: '内容为空',
          status: 'warning'
        });
        return;
      }

      const newChatList: ChatSiteItemType[] = [
        ...history,
        {
          dataId: nanoid(),
          obj: 'Human',
          value: val,
          status: 'finish'
        },
        {
          dataId: nanoid(),
          obj: 'AI',
          value: '',
          status: 'loading'
        }
      ];

      // 插入内容
      setChatHistory(newChatList);

      // 清空输入内容
      resetInputVal('');
      setQuestionGuide([]);
      setTimeout(() => {
        scrollToBottom();
      }, 100);
      try {
        // create abort obj
        const abortSignal = new AbortController();
        chatController.current = abortSignal;

        const messages = adaptChat2GptMessages({ messages: newChatList, reserveId: true });

        const {
          responseData,
          responseText,
          isNewChat = false
        } = await onStartChat({
          chatList: newChatList,
          messages,
          controller: abortSignal,
          generatingMessage,
          variables
        });

        isNewChatReplace.current = isNewChat;

        // set finish status
        setChatHistory((state) =>
          state.map((item, index) => {
            if (index !== state.length - 1) return item;
            return {
              ...item,
              status: 'finish',
              responseData
            };
          })
        );

        setTimeout(() => {
          createQuestionGuide({
            history: newChatList.map((item, i) =>
              i === newChatList.length - 1
                ? {
                    ...item,
                    value: responseText
                  }
                : item
            )
          });
          generatingScroll();
          isPc && TextareaDom.current?.focus();
        }, 100);
      } catch (err: any) {
        toast({
          title: getErrText(err, '聊天出错了~'),
          status: 'error',
          duration: 5000,
          isClosable: true
        });

        if (!err?.responseText) {
          resetInputVal(inputVal);
          setChatHistory(newChatList.slice(0, newChatList.length - 2));
        }

        // set finish status
        setChatHistory((state) =>
          state.map((item, index) => {
            if (index !== state.length - 1) return item;
            return {
              ...item,
              status: 'finish'
            };
          })
        );
      }
    },
    [
      chatHistory,
      onStartChat,
      isChatting,
      resetInputVal,
      toast,
      scrollToBottom,
      generatingMessage,
      createQuestionGuide,
      generatingScroll,
      isPc
    ]
  );

  // retry input
  const retryInput = useCallback(
    async (index: number) => {
      if (!onDelMessage) return;
      const delHistory = chatHistory.slice(index);

      setLoading(true);

      try {
        await Promise.all(
          delHistory.map((item, i) => onDelMessage({ contentId: item.dataId, index: index + i }))
        );
        setChatHistory((state) => (index === 0 ? [] : state.slice(0, index)));

        sendPrompt(variables, delHistory[0].value, chatHistory.slice(0, index));
      } catch (error) {}
      setLoading(false);
    },
    [chatHistory, onDelMessage, sendPrompt, setLoading, variables]
  );
  // delete one message
  const delOneMessage = useCallback(
    ({ dataId, index }: { dataId?: string; index: number }) => {
      setChatHistory((state) => state.filter((chat) => chat.dataId !== dataId));
      onDelMessage?.({
        contentId: dataId,
        index
      });
    },
    [onDelMessage]
  );

  // output data
  useImperativeHandle(ref, () => ({
    getChatHistories: () => chatHistory,
    resetVariables(e) {
      const defaultVal: Record<string, any> = {};
      variableModules?.forEach((item) => {
        defaultVal[item.key] = '';
      });

      reset(e || defaultVal);
      setVariables(e || defaultVal);
    },
    resetHistory(e) {
      setVariableInputFinish(!!e.length);
      setChatHistory(e);
    },
    scrollToBottom,
    sendPrompt: (question: string) => handleSubmit((item) => sendPrompt(item, question))()
  }));

  /* style start */
  const MessageCardStyle: BoxProps = {
    px: 4,
    py: 3,
    borderRadius: '0 8px 8px 8px',
    boxShadow: '0 0 8px rgba(0,0,0,0.15)',
    display: 'inline-block',
    maxW: ['calc(100% - 25px)', 'calc(100% - 40px)']
  };

  const showEmpty = useMemo(
    () =>
      feConfigs?.show_emptyChat &&
      showEmptyIntro &&
      chatHistory.length === 0 &&
      !variableModules?.length &&
      !welcomeText,
    [chatHistory.length, showEmptyIntro, variableModules, welcomeText]
  );
  const statusBoxData = useMemo(() => {
    const colorMap = {
      loading: 'myGray.700',
      running: '#67c13b',
      finish: 'blue.500'
    };
    if (!isChatting) return;
    const chatContent = chatHistory[chatHistory.length - 1];
    if (!chatContent) return;

    return {
      bg: colorMap[chatContent.status] || colorMap.loading,
      name: t(chatContent.moduleName || '') || t('common.Loading')
    };
  }, [chatHistory, isChatting, t]);
  /* style end */

  // page change and abort request
  useEffect(() => {
    isNewChatReplace.current = false;
    setQuestionGuide([]);
    return () => {
      chatController.current?.abort('leave');
      if (!isNewChatReplace.current) {
        questionGuideController.current?.abort('leave');
      }
    };
  }, [router.query]);

  // add listener
  useEffect(() => {
    const windowMessage = ({ data }: MessageEvent<{ type: 'sendPrompt'; text: string }>) => {
      if (data?.type === 'sendPrompt' && data?.text) {
        handleSubmit((item) => sendPrompt(item, data.text))();
      }
    };
    window.addEventListener('message', windowMessage);

    eventBus.on(EventNameEnum.sendQuestion, ({ text }: { text: string }) => {
      if (!text) return;
      handleSubmit((data) => sendPrompt(data, text))();
    });
    eventBus.on(EventNameEnum.editQuestion, ({ text }: { text: string }) => {
      if (!text) return;
      resetInputVal(text);
    });

    return () => {
      window.removeEventListener('message', windowMessage);
      eventBus.off(EventNameEnum.sendQuestion);
      eventBus.off(EventNameEnum.editQuestion);
    };
  }, [handleSubmit, resetInputVal, sendPrompt]);

  return (
    <Flex flexDirection={'column'} h={'100%'}>
      <Script src="/js/html2pdf.bundle.min.js" strategy="lazyOnload"></Script>

      {/* chat box container */}
      <Box ref={ChatBoxRef} flex={'1 0 0'} h={0} w={'100%'} overflow={'overlay'} px={[4, 0]} pb={3}>
        <Box id="chat-container" maxW={['100%', '92%']} h={'100%'} mx={'auto'}>
          {showEmpty && <Empty />}

          {!!welcomeText && (
            <Box py={3}>
              {/* avatar */}
              <ChatAvatar src={appAvatar} type={'AI'} />
              {/* message */}
              <Box textAlign={'left'}>
                <Card order={2} mt={2} {...MessageCardStyle} bg={'white'}>
                  <Markdown source={`~~~guide \n${welcomeText}`} isChatting={false} />
                </Card>
              </Box>
            </Box>
          )}
          {/* variable input */}
          {!!variableModules?.length && (
            <Box py={3}>
              {/* avatar */}
              <ChatAvatar src={appAvatar} type={'AI'} />
              {/* message */}
              <Box textAlign={'left'}>
                <Card order={2} mt={2} bg={'white'} w={'400px'} {...MessageCardStyle}>
                  {variableModules.map((item) => (
                    <Box key={item.id} mb={4}>
                      <VariableLabel required={item.required}>{item.label}</VariableLabel>
                      {item.type === VariableInputEnum.input && (
                        <Input
                          isDisabled={variableIsFinish}
                          bg={'myWhite.400'}
                          {...register(item.key, {
                            required: item.required
                          })}
                        />
                      )}
                      {item.type === VariableInputEnum.textarea && (
                        <Textarea
                          isDisabled={variableIsFinish}
                          bg={'myWhite.400'}
                          {...register(item.key, {
                            required: item.required
                          })}
                          rows={5}
                          maxLength={4000}
                        />
                      )}
                      {item.type === VariableInputEnum.select && (
                        <MySelect
                          width={'100%'}
                          isDisabled={variableIsFinish}
                          list={(item.enums || []).map((item) => ({
                            label: item.value,
                            value: item.value
                          }))}
                          {...register(item.key, {
                            required: item.required
                          })}
                          value={getValues(item.key)}
                          onchange={(e) => {
                            setValue(item.key, e);
                            setRefresh(!refresh);
                          }}
                        />
                      )}
                    </Box>
                  ))}
                  {!variableIsFinish && (
                    <Button
                      leftIcon={<MyIcon name={'chatFill'} w={'16px'} />}
                      size={'sm'}
                      maxW={'100px'}
                      borderRadius={'lg'}
                      onClick={handleSubmit((data) => {
                        onUpdateVariable?.(data);
                        setVariables(data);
                        setVariableInputFinish(true);
                      })}
                    >
                      {'开始对话'}
                    </Button>
                  )}
                </Card>
              </Box>
            </Box>
          )}

          {/* chat history */}
          <Box id={'history'}>
            {chatHistory.map((item, index) => (
              <Box key={item.dataId} py={5}>
                {item.obj === 'Human' && (
                  <>
                    {/* control icon */}
                    <Flex w={'100%'} alignItems={'center'} justifyContent={'flex-end'}>
                      <ChatController
                        chat={item}
                        onDelete={
                          onDelMessage
                            ? () => {
                                delOneMessage({ dataId: item.dataId, index });
                              }
                            : undefined
                        }
                        onRetry={() => retryInput(index)}
                      />
                      <ChatAvatar src={userAvatar} type={'Human'} />
                    </Flex>
                    {/* content */}
                    <Box mt={['6px', 2]} textAlign={'right'}>
                      <Card
                        className="markdown"
                        {...MessageCardStyle}
                        bg={'blue.200'}
                        borderRadius={'8px 0 8px 8px'}
                        textAlign={'left'}
                      >
                        <Markdown source={item.value} isChatting={false} />
                      </Card>
                    </Box>
                  </>
                )}
                {item.obj === 'AI' && (
                  <>
                    {/* control icon */}
                    <Flex w={'100%'} alignItems={'center'}>
                      <ChatAvatar src={appAvatar} type={'AI'} />
                      <ChatController
                        ml={2}
                        chat={item}
                        setChatHistory={setChatHistory}
                        display={index === chatHistory.length - 1 && isChatting ? 'none' : 'flex'}
                        showVoiceIcon={showVoiceIcon}
                        ttsConfig={ttsConfig}
                        onDelete={
                          onDelMessage
                            ? () => {
                                delOneMessage({ dataId: item.dataId, index });
                              }
                            : undefined
                        }
                        onMark={
                          showMarkIcon
                            ? () => {
                                if (!item.dataId) return;
                                if (item.adminFeedback) {
                                  setAdminMarkData({
                                    chatItemId: item.dataId,
                                    datasetId: item.adminFeedback.datasetId,
                                    collectionId: item.adminFeedback.collectionId,
                                    dataId: item.adminFeedback.dataId,
                                    q: item.adminFeedback.q || chatHistory[index - 1]?.value || '',
                                    a: item.adminFeedback.a
                                  });
                                } else {
                                  setAdminMarkData({
                                    chatItemId: item.dataId,
                                    q: chatHistory[index - 1]?.value || '',
                                    a: item.value
                                  });
                                }
                              }
                            : undefined
                        }
                        onAddUserLike={(() => {
                          if (feedbackType !== FeedbackTypeEnum.user || item.userBadFeedback) {
                            return;
                          }
                          return () => {
                            if (!item.dataId || !chatId || !appId) return;

                            const isGoodFeedback = !!item.userGoodFeedback;
                            setChatHistory((state) =>
                              state.map((chatItem) =>
                                chatItem.dataId === item.dataId
                                  ? {
                                      ...chatItem,
                                      userGoodFeedback: isGoodFeedback ? undefined : 'yes'
                                    }
                                  : chatItem
                              )
                            );
                            try {
                              updateChatUserFeedback({
                                appId,
                                chatId,
                                chatItemId: item.dataId,
                                shareId,
                                outLinkUid,
                                userGoodFeedback: isGoodFeedback ? undefined : 'yes'
                              });
                            } catch (error) {}
                          };
                        })()}
                        onCloseUserLike={
                          feedbackType === FeedbackTypeEnum.admin
                            ? () => {
                                if (!item.dataId || !chatId || !appId) return;
                                setChatHistory((state) =>
                                  state.map((chatItem) =>
                                    chatItem.dataId === item.dataId
                                      ? { ...chatItem, userGoodFeedback: undefined }
                                      : chatItem
                                  )
                                );
                                updateChatUserFeedback({
                                  appId,
                                  chatId,
                                  chatItemId: item.dataId,
                                  userGoodFeedback: undefined
                                });
                              }
                            : undefined
                        }
                        onAddUserDislike={(() => {
                          if (feedbackType !== FeedbackTypeEnum.user || item.userGoodFeedback) {
                            return;
                          }
                          if (item.userBadFeedback) {
                            return () => {
                              if (!item.dataId || !chatId || !appId) return;
                              setChatHistory((state) =>
                                state.map((chatItem) =>
                                  chatItem.dataId === item.dataId
                                    ? { ...chatItem, userBadFeedback: undefined }
                                    : chatItem
                                )
                              );
                              try {
                                updateChatUserFeedback({
                                  appId,
                                  chatId,
                                  chatItemId: item.dataId,
                                  shareId,
                                  outLinkUid
                                });
                              } catch (error) {}
                            };
                          } else {
                            return () => setFeedbackId(item.dataId);
                          }
                        })()}
                        onReadUserDislike={
                          feedbackType === FeedbackTypeEnum.admin
                            ? () => {
                                if (!item.dataId) return;
                                setReadFeedbackData({
                                  chatItemId: item.dataId || '',
                                  content: item.userBadFeedback || ''
                                });
                              }
                            : undefined
                        }
                      />
                      {/* chatting status */}
                      {statusBoxData && index === chatHistory.length - 1 && (
                        <Flex
                          ml={3}
                          alignItems={'center'}
                          px={3}
                          py={'1px'}
                          borderRadius="md"
                          border={theme.borders.base}
                        >
                          <Box
                            className={styles.statusAnimation}
                            bg={statusBoxData.bg}
                            w="8px"
                            h="8px"
                            borderRadius={'50%'}
                            mt={'1px'}
                          ></Box>
                          <Box ml={2} color={'myGray.600'}>
                            {statusBoxData.name}
                          </Box>
                        </Flex>
                      )}
                    </Flex>
                    {/* content */}
                    <Box textAlign={'left'} mt={['6px', 2]}>
                      <Card bg={'white'} {...MessageCardStyle}>
                        <Markdown
                          source={(() => {
                            const text = item.value as string;

                            // replace quote tag: [source1] 标识第一个来源，需要提取数字1，从而去数组里查找来源
                            const quoteReg = /\[source:(.+)\]/g;
                            const replaceText = text.replace(quoteReg, `[QUOTE SIGN]($1)`);

                            // question guide
                            if (
                              index === chatHistory.length - 1 &&
                              !isChatting &&
                              questionGuides.length > 0
                            ) {
                              return `${replaceText}\n\`\`\`${
                                CodeClassName.questionGuide
                              }\n${JSON.stringify(questionGuides)}`;
                            }
                            return replaceText;
                          })()}
                          isChatting={index === chatHistory.length - 1 && isChatting}
                        />

                        <ResponseTags responseData={item.responseData} isShare={!!shareId} />

                        {/* custom feedback */}
                        {item.customFeedbacks && item.customFeedbacks.length > 0 && (
                          <Box>
                            <ChatBoxDivider
                              icon={'core/app/customFeedback'}
                              text={t('core.app.feedback.Custom feedback')}
                            />
                            {item.customFeedbacks.map((text, i) => (
                              <Box key={`${text}${i}`}>
                                <MyTooltip label={t('core.app.feedback.close custom feedback')}>
                                  <Checkbox
                                    onChange={(e) => {
                                      if (e.target.checked && appId && chatId && item.dataId) {
                                        closeCustomFeedback({
                                          appId,
                                          chatId,
                                          chatItemId: item.dataId,
                                          index: i
                                        });
                                        // update dom
                                        setChatHistory((state) =>
                                          state.map((chatItem) =>
                                            chatItem.dataId === item.dataId
                                              ? {
                                                  ...chatItem,
                                                  customFeedbacks: chatItem.customFeedbacks?.filter(
                                                    (item, index) => index !== i
                                                  )
                                                }
                                              : chatItem
                                          )
                                        );
                                      }
                                      console.log(e);
                                    }}
                                  >
                                    {text}
                                  </Checkbox>
                                </MyTooltip>
                              </Box>
                            ))}
                          </Box>
                        )}
                        {/* admin mark content */}
                        {showMarkIcon && item.adminFeedback && (
                          <Box>
                            <ChatBoxDivider
                              icon="core/app/markLight"
                              text={t('chat.Admin Mark Content')}
                            />
                            <Box whiteSpace={'pre'}>{`${item.adminFeedback.q || ''}${
                              item.adminFeedback.a ? `\n${item.adminFeedback.a}` : ''
                            }`}</Box>
                          </Box>
                        )}
                      </Card>
                    </Box>
                  </>
                )}
              </Box>
            ))}
          </Box>
        </Box>
      </Box>
      {/* message input */}
      {onStartChat && variableIsFinish && active ? (
        <MessageInput
          onChange={(e) => {
            setRefresh(!refresh);
          }}
          onSendMessage={(e) => {
            handleSubmit((data) => sendPrompt(data, e))();
          }}
          onStop={() => chatController.current?.abort('stop')}
          isChatting={isChatting}
          TextareaDom={TextareaDom}
          resetInputVal={resetInputVal}
          showFileSelector={showFileSelector}
        />
      ) : null}
      {/* user feedback modal */}
      {!!feedbackId && chatId && appId && (
        <FeedbackModal
          appId={appId}
          chatId={chatId}
          chatItemId={feedbackId}
          onClose={() => setFeedbackId(undefined)}
          onSuccess={(content: string) => {
            setChatHistory((state) =>
              state.map((item) =>
                item.dataId === feedbackId ? { ...item, userBadFeedback: content } : item
              )
            );
            setFeedbackId(undefined);
          }}
        />
      )}
      {/* admin read feedback modal */}
      {!!readFeedbackData && (
        <ReadFeedbackModal
          content={readFeedbackData.content}
          onClose={() => setReadFeedbackData(undefined)}
          onCloseFeedback={() => {
            setChatHistory((state) =>
              state.map((chatItem) =>
                chatItem.dataId === readFeedbackData.chatItemId
                  ? { ...chatItem, userBadFeedback: undefined }
                  : chatItem
              )
            );
            try {
              if (!chatId || !appId) return;
              updateChatUserFeedback({
                appId,
                chatId,
                chatItemId: readFeedbackData.chatItemId
              });
            } catch (error) {}
            setReadFeedbackData(undefined);
          }}
        />
      )}
      {/* admin mark data */}
      {!!adminMarkData && (
        <SelectMarkCollection
          adminMarkData={adminMarkData}
          setAdminMarkData={(e) => setAdminMarkData({ ...e, chatItemId: adminMarkData.chatItemId })}
          onClose={() => setAdminMarkData(undefined)}
          onSuccess={(adminFeedback) => {
            if (!appId || !chatId || !adminMarkData.chatItemId) return;
            updateChatAdminFeedback({
              appId,
              chatId,
              chatItemId: adminMarkData.chatItemId,
              ...adminFeedback
            });
            // update dom
            setChatHistory((state) =>
              state.map((chatItem) =>
                chatItem.dataId === adminMarkData.chatItemId
                  ? {
                      ...chatItem,
                      adminFeedback
                    }
                  : chatItem
              )
            );

            if (readFeedbackData && chatId && appId) {
              updateChatUserFeedback({
                appId,
                chatId,
                chatItemId: readFeedbackData.chatItemId,
                userBadFeedback: undefined
              });
              setChatHistory((state) =>
                state.map((chatItem) =>
                  chatItem.dataId === readFeedbackData.chatItemId
                    ? { ...chatItem, userBadFeedback: undefined }
                    : chatItem
                )
              );
              setReadFeedbackData(undefined);
            }
          }}
        />
      )}
    </Flex>
  );
};

export default React.memo(forwardRef(ChatBox));

export const useChatBox = () => {
  const onExportChat = useCallback(
    ({ type, history }: { type: ExportChatType; history: ChatItemType[] }) => {
      const getHistoryHtml = () => {
        const historyDom = document.getElementById('history');
        if (!historyDom) return;
        const dom = Array.from(historyDom.children).map((child, i) => {
          const avatar = `<img src="${child.querySelector<HTMLImageElement>('.avatar')
            ?.src}" alt="" />`;

          const chatContent = child.querySelector<HTMLDivElement>('.markdown');

          if (!chatContent) {
            return '';
          }

          const chatContentClone = chatContent.cloneNode(true) as HTMLDivElement;

          const codeHeader = chatContentClone.querySelectorAll('.code-header');
          codeHeader.forEach((childElement: any) => {
            childElement.remove();
          });

          return `<div class="chat-item">
          ${avatar}
          ${chatContentClone.outerHTML}
        </div>`;
        });

        const html = htmlTemplate.replace('{{CHAT_CONTENT}}', dom.join('\n'));
        return html;
      };

      const map: Record<ExportChatType, () => void> = {
        md: () => {
          fileDownload({
            text: history.map((item) => item.value).join('\n\n'),
            type: 'text/markdown',
            filename: 'chat.md'
          });
        },
        html: () => {
          const html = getHistoryHtml();
          html &&
            fileDownload({
              text: html,
              type: 'text/html',
              filename: '聊天记录.html'
            });
        },
        pdf: () => {
          const html = getHistoryHtml();

          html &&
            // @ts-ignore
            html2pdf(html, {
              margin: 0,
              filename: `聊天记录.pdf`
            });
        }
      };

      map[type]();
    },
    []
  );

  return {
    onExportChat
  };
};

function VariableLabel({
  required = false,
  children
}: {
  required?: boolean;
  children: React.ReactNode | string;
}) {
  return (
    <Box as={'label'} display={'inline-block'} position={'relative'} mb={1}>
      {children}
      {required && (
        <Box
          position={'absolute'}
          top={'-2px'}
          right={'-10px'}
          color={'red.500'}
          fontWeight={'bold'}
        >
          *
        </Box>
      )}
    </Box>
  );
}
function ChatAvatar({ src, type }: { src?: string; type: 'Human' | 'AI' }) {
  const theme = useTheme();
  return (
    <Box
      w={['28px', '34px']}
      h={['28px', '34px']}
      p={'2px'}
      borderRadius={'lg'}
      border={theme.borders.base}
      boxShadow={'0 0 5px rgba(0,0,0,0.1)'}
      bg={type === 'Human' ? 'white' : 'blue.50'}
    >
      <Avatar src={src} w={'100%'} h={'100%'} />
    </Box>
  );
}

function Empty() {
  const { data: chatProblem } = useMarkdown({ url: '/chatProblem.md' });
  const { data: versionIntro } = useMarkdown({ url: '/versionIntro.md' });

  return (
    <Box pt={6} w={'85%'} maxW={'600px'} m={'auto'} alignItems={'center'} justifyContent={'center'}>
      {/* version intro */}
      <Card p={4} mb={10} minH={'200px'}>
        <Markdown source={versionIntro} />
      </Card>
      <Card p={4} minH={'600px'}>
        <Markdown source={chatProblem} />
      </Card>
    </Box>
  );
}

function ChatController({
  chat,
  setChatHistory,
  display,
  showVoiceIcon,
  ttsConfig,
  onReadUserDislike,
  onCloseUserLike,
  onMark,
  onRetry,
  onDelete,
  onAddUserDislike,
  onAddUserLike,
  ml,
  mr
}: {
  chat: ChatSiteItemType;
  setChatHistory?: React.Dispatch<React.SetStateAction<ChatSiteItemType[]>>;
  showVoiceIcon?: boolean;
  ttsConfig?: AppTTSConfigType;
  onRetry?: () => void;
  onDelete?: () => void;
  onMark?: () => void;
  onReadUserDislike?: () => void;
  onCloseUserLike?: () => void;
  onAddUserLike?: () => void;
  onAddUserDislike?: () => void;
} & FlexProps) {
  const theme = useTheme();
  const { t } = useTranslation();
  const { copyData } = useCopyData();
  const { audioLoading, audioPlaying, hasAudio, playAudio, cancelAudio } = useAudioPlay({
    ttsConfig
  });
  const controlIconStyle = {
    w: '14px',
    cursor: 'pointer',
    p: 1,
    bg: 'white',
    borderRadius: 'lg',
    boxShadow: '0 0 5px rgba(0,0,0,0.1)',
    border: theme.borders.base,
    mr: 3
  };
  const controlContainerStyle = {
    className: 'control',
    color: 'myGray.400',
    display: 'flex',
    pl: 1
  };

  return (
    <Flex {...controlContainerStyle} ml={ml} mr={mr} display={display}>
      <MyTooltip label={'复制'}>
        <MyIcon
          {...controlIconStyle}
          name={'copy'}
          _hover={{ color: 'blue.600' }}
          onClick={() => copyData(chat.value)}
        />
      </MyTooltip>
      {!!onDelete && (
        <>
          {onRetry && (
            <MyTooltip label={t('chat.retry')}>
              <MyIcon
                {...controlIconStyle}
                name={'common/retryLight'}
                _hover={{ color: 'green.500' }}
                onClick={onRetry}
              />
            </MyTooltip>
          )}
          <MyTooltip label={'删除'}>
            <MyIcon
              {...controlIconStyle}
              name={'delete'}
              _hover={{ color: 'red.600' }}
              onClick={onDelete}
            />
          </MyTooltip>
        </>
      )}
      {showVoiceIcon &&
        hasAudio &&
        (audioLoading ? (
          <MyTooltip label={'加载中...'}>
            <MyIcon {...controlIconStyle} name={'common/loading'} />
          </MyTooltip>
        ) : audioPlaying ? (
          <Flex alignItems={'center'} mr={2}>
            <MyTooltip label={t('core.chat.tts.Stop Speech')}>
              <MyIcon
                {...controlIconStyle}
                mr={1}
                name={'core/chat/stopSpeech'}
                color={'#E74694'}
                onClick={() => cancelAudio()}
              />
            </MyTooltip>
            <Image src="/icon/speaking.gif" w={'23px'} alt={''} />
          </Flex>
        ) : (
          <MyTooltip label={t('core.app.TTS')}>
            <MyIcon
              {...controlIconStyle}
              name={'voice'}
              _hover={{ color: '#E74694' }}
              onClick={async () => {
                const response = await playAudio({
                  buffer: chat.ttsBuffer,
                  chatItemId: chat.dataId,
                  text: chat.value
                });

                if (!setChatHistory || !response.buffer) return;
                setChatHistory((state) =>
                  state.map((item) =>
                    item.dataId === chat.dataId
                      ? {
                          ...item,
                          ttsBuffer: response.buffer
                        }
                      : item
                  )
                );
              }}
            />
          </MyTooltip>
        ))}
      {!!onMark && (
        <MyTooltip label={t('chat.Mark')}>
          <MyIcon
            {...controlIconStyle}
            name={'core/app/markLight'}
            _hover={{ color: '#67c13b' }}
            onClick={onMark}
          />
        </MyTooltip>
      )}
      {!!onCloseUserLike && chat.userGoodFeedback && (
        <MyTooltip label={t('core.chat.feedback.Close User Like')}>
          <MyIcon
            {...controlIconStyle}
            color={'white'}
            bg={'green.500'}
            fontWeight={'bold'}
            name={'core/chat/feedback/goodLight'}
            onClick={onCloseUserLike}
          />
        </MyTooltip>
      )}
      {!!onReadUserDislike && chat.userBadFeedback && (
        <MyTooltip label={t('core.chat.feedback.Read User dislike')}>
          <MyIcon
            {...controlIconStyle}
            color={'white'}
            bg={'#FC9663'}
            fontWeight={'bold'}
            name={'core/chat/feedback/badLight'}
            onClick={onReadUserDislike}
          />
        </MyTooltip>
      )}
      {!!onAddUserLike && (
        <MyIcon
          {...controlIconStyle}
          {...(!!chat.userGoodFeedback
            ? {
                color: 'white',
                bg: 'green.500',
                fontWeight: 'bold'
              }
            : {
                _hover: { color: 'green.600' }
              })}
          name={'core/chat/feedback/goodLight'}
          onClick={onAddUserLike}
        />
      )}
      {!!onAddUserDislike && (
        <MyIcon
          {...controlIconStyle}
          {...(!!chat.userBadFeedback
            ? {
                color: 'white',
                bg: '#FC9663',
                fontWeight: 'bold',
                onClick: onAddUserDislike
              }
            : {
                _hover: { color: '#FB7C3C' },
                onClick: onAddUserDislike
              })}
          name={'core/chat/feedback/badLight'}
        />
      )}
    </Flex>
  );
}
