import { LargeModelFormFieldWithoutFilter } from '@/components/large-model-form-field';
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
import {
  NextMessageInput,
  NextMessageInputOnPressEnterParameter,
} from '@/components/message-input/next';
import MessageItem from '@/components/message-item';
import PdfSheet from '@/components/pdf-drawer';
import { useClickDrawer } from '@/components/pdf-drawer/hooks';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Form } from '@/components/ui/form';
import {
  Tooltip,
  TooltipContent,
  TooltipTrigger,
} from '@/components/ui/tooltip';
import { MessageType } from '@/constants/chat';
import {
  useHandleMessageInputChange,
  useRegenerateMessage,
  useScrollToBottom,
} from '@/hooks/logic-hooks';
import {
  useFetchChat,
  useGetChatSearchParams,
  usePatchChat,
} from '@/hooks/use-chat-request';
import { useFindLlmByUuid } from '@/hooks/use-llm-request';
import { useFetchUserInfo } from '@/hooks/use-user-setting-request';
import { IClientConversation, IMessage } from '@/interfaces/database/chat';
import { buildMessageUuidWithRole } from '@/utils/chat';
import { zodResolver } from '@hookform/resolvers/zod';
import { t } from 'i18next';
import { isEmpty, omit, trim } from 'lodash';
import { ListCheck, Plus, Trash2 } from 'lucide-react';
import {
  forwardRef,
  useCallback,
  useEffect,
  useImperativeHandle,
  useLayoutEffect,
  useRef,
  useState,
} from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useParams } from 'react-router';
import { z } from 'zod';
import {
  useGetSendButtonDisabled,
  useSendButtonDisabled,
} from '../../hooks/use-button-disabled';
import { useCreateConversationBeforeUploadDocument } from '../../hooks/use-create-conversation';
import {
  HandlePressEnterType,
  useSendSingleMessage,
  UseSendSingleMessageParameter,
} from '../../hooks/use-send-single-message';
import { useUploadFile } from '../../hooks/use-upload-file';
import { buildMessageItemReference } from '../../utils';
import { useAddChatBox } from '../use-add-box';
import { useShowInternet } from '../use-show-internet';

type MultipleChatBoxProps = {
  controller: AbortController;
  chatBoxIds: string[];
  stopOutputMessage(): void;
  conversation: IClientConversation;
} & Pick<
  ReturnType<typeof useAddChatBox>,
  'removeChatBox' | 'addChatBox' | 'chatBoxIds'
>;

type ChatCardProps = {
  id: string;
  idx: number;
  conversation: IClientConversation;
  setLoading(id: string, loading: boolean): void;
} & Pick<
  MultipleChatBoxProps,
  'controller' | 'removeChatBox' | 'addChatBox' | 'chatBoxIds'
> &
  Pick<ReturnType<typeof useClickDrawer>, 'clickDocumentButton'> &
  UseSendSingleMessageParameter;

const ChatCard = forwardRef(function ChatCard(
  {
    controller,
    removeChatBox,
    id,
    idx,
    addChatBox,
    chatBoxIds,
    clickDocumentButton,
    conversation,
    value,
    setValue,
    files,
    clearFiles,
    setLoading,
  }: ChatCardProps,
  ref,
) {
  const { id: dialogId } = useParams();
  const { patchChat } = usePatchChat();

  const {
    removeMessageById,
    derivedMessages,
    handlePressEnter,
    sendLoading,
    sendMessage,
    removeMessagesAfterCurrentMessage,
  } = useSendSingleMessage({
    controller,
    value,
    setValue,
    files,
    clearFiles,
  });

  const messageContainerRef = useRef<HTMLDivElement>(null);

  const { scrollRef } = useScrollToBottom(derivedMessages, messageContainerRef);

  const FormSchema = z.object(LlmSettingSchema);

  const form = useForm<z.infer<typeof FormSchema>>({
    resolver: zodResolver(FormSchema),
    defaultValues: {
      llm_id: '',
    },
  });

  const llmId = useWatch({ control: form.control, name: 'llm_id' });

  // Regenerate within this card: reuse the card's own message state and
  // resend with the card's model settings (llm_id, temperature, ...).
  const sendCardMessage = useCallback(
    ({ message, messages }: { message: IMessage; messages?: IMessage[] }) =>
      sendMessage({ message, messages, ...form.getValues(), storeHistoryMessages: false, omitSessionId: true }),
    [sendMessage, form],
  );

  const { regenerateMessage } = useRegenerateMessage({
    removeMessagesAfterCurrentMessage,
    sendMessage: sendCardMessage,
    messages: derivedMessages,
  });

  const { data: userInfo } = useFetchUserInfo();
  const { data: currentDialog } = useFetchChat();
  const findLlmByUuid = useFindLlmByUuid();

  useLayoutEffect(() => {
    form.setValue('llm_id', currentDialog?.llm_id || '');
  }, [currentDialog?.llm_id, form]);

  const isLatestChat = idx === chatBoxIds.length - 1;

  const handleRemoveChatBox = useCallback(() => {
    removeChatBox(id);
  }, [id, removeChatBox]);

  const handleApplyConfig = useCallback(() => {
    const values = form.getValues();
    const llmId = values.llm_id;
    patchChat({
      chatId: dialogId!,
      params: {
        ...currentDialog,
        llm_id: llmId,
        llm_setting: {
          ...omit(values, 'llm_id'),
          model_type: findLlmByUuid(llmId)?.model_type || 'chat',
        },
      },
    });
  }, [currentDialog, dialogId, form, patchChat, findLlmByUuid]);

  useImperativeHandle(
    ref,
    (): HandlePressEnterType => (params) =>
      handlePressEnter({ ...params, ...form.getValues(), storeHistoryMessages: false, omitSessionId: true }),
  );

  useEffect(() => {
    setLoading(id, sendLoading);
  }, [id, sendLoading, setLoading]);

  return (
    <Card
      className="bg-transparent border flex-1 flex flex-col"
      data-testid="chat-detail-multimodel-card"
      data-card-index={idx}
      data-card-key={id}
    >
      <CardHeader className="border-b-0.5 px-5 py-3">
        <CardTitle className="flex justify-between items-center">
          <div className="flex items-center gap-3">
            <span className="text-base">{idx + 1}</span>
            <Form {...form}>
              <LargeModelFormFieldWithoutFilter
                triggerTestId="chat-detail-multimodel-card-model-select"
                optionTestIdPrefix="chat-detail-llm-option-"
                ownerTenantId={currentDialog?.tenant_id}
              ></LargeModelFormFieldWithoutFilter>
            </Form>
          </div>
          <div className="space-x-2">
            <Tooltip>
              <TooltipTrigger asChild>
                <Button
                  variant="ghost"
                  size="icon-sm"
                  disabled={isEmpty(llmId)}
                  onClick={handleApplyConfig}
                  data-testid="chat-detail-multimodel-card-apply"
                  data-card-index={idx}
                >
                  <ListCheck />
                </Button>
              </TooltipTrigger>
              <TooltipContent>
                <p>{t('chat.applyModelConfigs')}</p>
              </TooltipContent>
            </Tooltip>
            {chatBoxIds.length > 1 && (
              <Button
                variant="ghost"
                size="icon-sm"
                onClick={handleRemoveChatBox}
                data-testid="chat-detail-multimodel-card-remove"
                data-card-index={idx}
              >
                <Trash2 />
              </Button>
            )}
            {isLatestChat && idx < 2 && (
              <Button
                variant="ghost"
                size="icon-sm"
                onClick={addChatBox}
                data-testid="chat-detail-multimodel-add-card"
              >
                <Plus />
              </Button>
            )}
          </div>
        </CardTitle>
      </CardHeader>
      <CardContent className="flex-1 min-h-0">
        <div ref={messageContainerRef} className="h-full overflow-auto">
          <div className="w-full">
            {derivedMessages?.map((message, i) => {
              return (
                <MessageItem
                  loading={
                    message.role === MessageType.Assistant &&
                    sendLoading &&
                    derivedMessages.length - 1 === i
                  }
                  key={buildMessageUuidWithRole(message)}
                  item={message}
                  nickname={userInfo.nickname}
                  avatar={userInfo.avatar}
                  avatarDialog={currentDialog.icon}
                  reference={buildMessageItemReference(
                    {
                      messages: derivedMessages,
                      reference: conversation.reference,
                    },
                    message,
                  )}
                  // clickDocumentButton={clickDocumentButton}
                  index={i}
                  removeMessageById={removeMessageById}
                  regenerateMessage={regenerateMessage}
                  sendLoading={sendLoading}
                  clickDocumentButton={clickDocumentButton}
                  showLikeButton={false}
                ></MessageItem>
              );
            })}
          </div>
          <div ref={scrollRef} />
        </div>
      </CardContent>
    </Card>
  );
});

export function MultipleChatBox({
  controller,
  chatBoxIds,
  removeChatBox,
  addChatBox,
  stopOutputMessage,
  conversation,
}: MultipleChatBoxProps) {
  const { createConversationBeforeUploadDocument } =
    useCreateConversationBeforeUploadDocument();
  const { conversationId } = useGetChatSearchParams();
  const disabled = useGetSendButtonDisabled();
  const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
    useClickDrawer();

  const [chatBoxLoading, setChatBoxLoading] = useState<Map<string, boolean>>(
    new Map(),
  );

  const setLoading = useCallback((id: string, loading: boolean) => {
    setChatBoxLoading((prev) => {
      const newMap = new Map(prev);
      newMap.set(id, loading);
      return newMap;
    });
  }, []);

  const allChatBoxLoading = [...chatBoxLoading.values()];

  const showInternet = useShowInternet();

  const { handleInputChange, value, setValue } = useHandleMessageInputChange();
  const { handleUploadFile, isUploading, files, clearFiles, removeFile } =
    useUploadFile();
  const sendDisabled = useSendButtonDisabled(value);

  const boxesRef = useRef<Record<string, HandlePressEnterType>>({});

  const setFormRef = (id: string) => (ref: HandlePressEnterType) => {
    boxesRef.current[id] = ref;
  };

  const handlePressEnter = useCallback(
    async ({
      enableInternet,
      enableThinking,
    }: NextMessageInputOnPressEnterParameter) => {
      if (trim(value) === '') return;

      Object.values(boxesRef.current).forEach((box) => {
        box?.({
          enableInternet,
          enableThinking,
        });
      });
    },
    [value],
  );

  return (
    <section className="flex flex-1 min-h-0 flex-col px-5">
      <div
        className="flex gap-4 flex-1 px-5 pb-14 min-h-0"
        data-testid="chat-detail-multimodel-grid"
      >
        {chatBoxIds.map((id, idx) => (
          <ChatCard
            key={id}
            idx={idx}
            controller={controller}
            id={id}
            chatBoxIds={chatBoxIds}
            removeChatBox={removeChatBox}
            addChatBox={addChatBox}
            ref={setFormRef(id)}
            clickDocumentButton={clickDocumentButton}
            conversation={conversation}
            value={value}
            files={files}
            setValue={setValue}
            clearFiles={clearFiles}
            setLoading={setLoading}
          ></ChatCard>
        ))}
      </div>
      <div className="px-[20%]">
        <NextMessageInput
          disabled={disabled}
          sendDisabled={sendDisabled}
          sendLoading={allChatBoxLoading.some((loading) => loading)}
          value={value}
          resize="vertical"
          onInputChange={handleInputChange}
          onPressEnter={handlePressEnter}
          conversationId={conversationId}
          createConversationBeforeUploadDocument={
            createConversationBeforeUploadDocument
          }
          stopOutputMessage={stopOutputMessage}
          onUpload={handleUploadFile}
          showReasoning
          showInternet={showInternet}
          removeFile={removeFile}
          isUploading={isUploading}
        />
      </div>
      {visible && (
        <PdfSheet
          visible={visible}
          hideModal={hideModal}
          documentId={documentId}
          chunk={selectedChunk}
        ></PdfSheet>
      )}
    </section>
  );
}
