import { FC, useCallback, useEffect, useState } from 'react';
import { Container } from 'components/layout';
import { Visible } from 'components';
import { useProfile, useResponsiveLayout } from 'hooks';
import { debounce, omit, uniqueId } from 'lodash';
import { useReactiveVar } from '@apollo/client';
import { editingMessageIdVar, isEditingMessageVar } from 'state-management/chat';
import { RichTextVariant } from 'components/rich-text/Types';
import { removeDraftId, setDraftId } from 'utils/message';
import { metrics } from 'config';
import { Editor } from 'components/editor';
import { EditorState } from 'lexical';
import { parseLexicalContentToLocal } from 'utils/richText';
import {
  CreateDraftMessageMutation,
  MessageFragment,
  MessageTypeEnum,
  RichTextContentSegmentString,
  RichTextContentSegmentTypeEnum,
  SendMessageMutation,
  useCreateDraftMessageMutation,
  useSendMessageMutation,
  useUpdateMessageMutation,
  MessageStatusEnum,
  RichTextContentSegmentStringInput,
  MultipleMessagesDocument,
  Block,
  User,
  RichTextContentSegment,
} from 'expo-schema';
import { ReferenceMessage } from '../rich-text/reference-message';

const { LIMITS } = metrics;
const { INPUT_DEBOUNCE } = metrics.RICH_TEXT_EDITOR;

export const ChatInput: FC<ChatInputProps> = ({ roomId, message }) => {
  const { refetchDrafts, drafts, removeDraft, profile } = useProfile();
  const { isSmall } = useResponsiveLayout();

  const [draft, setDraft] = useState<MessageFragment>();
  const isEditingMessage = useReactiveVar(isEditingMessageVar);
  const editingMessageId = useReactiveVar(editingMessageIdVar);

  const draftId = draft?.id;

  const [richTextContent, setRichTextContent] = useState<RichTextContentProps>({
    id: uniqueId(),
    strings: [],
  });

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

  const handleDrafts = useCallback(async () => {
    const foundDraft = drafts.find((draftItem) => draftItem.room?.id === roomId);
    if (foundDraft) {
      const cleanedDraft = omit(foundDraft, '__typename');
      setDraft(cleanedDraft);
      await setDraftId(cleanedDraft.id);
    } else {
      setDraft(undefined);
      await removeDraftId();
    }
  }, [drafts, roomId]);

  useEffect(() => {
    handleDrafts();
  }, [drafts, handleDrafts, profile, roomId]);

  useEffect(() => {
    const contents = draft?.richTextContent;
    const isDraftContent = contents && contents?.length > 0;
    const isAlreadyContent = richTextContent.strings.length > 0;
    const isRichTextContentValid = isDraftContent && !isAlreadyContent;
    if (isRichTextContentValid) {
      if (contents) {
        const strings = contents[0].strings as RichTextContentSegmentString[];
        setRichTextContent({
          id: draft?.id as string,
          strings,
        });
      }
    }
  }, [draft, richTextContent.strings.length]);

  useEffect(() => {
    // This will only execute when draft have been remove from backend and on reload we need to remove from input
    if (!draft) {
      setRichTextContent({
        id: uniqueId(),
        strings: [],
      });
    }
  }, [draft]);

  const getParsedStrings = useCallback(
    (preParsedStrings: RichTextContentSegmentString[]): RichTextContentSegmentStringInput[] => {
      return preParsedStrings.map(({ id, style, __typename, ...string }, index) => {
        const innerStyle = omit(style || {}, '__typename');
        return {
          value: string.value,
          onNewLine: string.onNewLine,
          style: innerStyle,
          order: index,
        };
      });
    },
    []
  );

  const getParsedStringsForOptimistic = useCallback(
    (preParsedStrings: RichTextContentSegmentString[]): RichTextContentSegmentString[] => {
      return preParsedStrings.map(({ id, style, __typename, ...string }, index) => {
        return {
          __typename: 'RichTextContentSegmentString',
          id: uniqueId(),
          mentionedBlock: { id: '' } as Block,
          mentionedUser: { id: '', name: { first: '', last: '' }, profilePhotoUrl: '' } as User,
          mentionedDate: new Date(),
          mentionedUserId: '',
          value: string.value,
          onNewLine: string.onNewLine || false,
          linkUrl: string.linkUrl || '',
          style: style,
          order: index,
        };
      });
    },
    []
  );

  const createDraftComplete = useCallback(
    async (draftResponse: CreateDraftMessageMutation) => {
      refetchDrafts();
      const draftRes = draftResponse.createDraftMessage.message;
      const id = draftResponse.createDraftMessage.message.id;
      setDraft(draftRes);
      await setDraftId(id);
    },
    [refetchDrafts]
  );

  const sendMessageComplete = useCallback(
    (response: SendMessageMutation) => {
      if (response.sendMessage.message.id) {
        setDraft(undefined);
        removeDraft(response.sendMessage.message.id);
        setRichTextContent({ id: '', strings: [] });
      } else {
        if (draftId) {
          removeDraft(draftId);
        }
      }
    },
    [draftId, removeDraft]
  );

  const [createDraftMessage] = useCreateDraftMessageMutation({
    onCompleted: createDraftComplete,
  });

  const [updateMessage] = useUpdateMessageMutation({});

  const [sendMessage] = useSendMessageMutation({
    onCompleted: sendMessageComplete,
    update: async (cache, { data }) => {
      const cacheMessages: any = cache.readQuery({
        query: MultipleMessagesDocument,
        optimistic: true,
        variables: {
          input: {
            status: MessageStatusEnum.Sent,
            roomId,
            limit: LIMITS.messages,
          },
        },
      });
      const newMessage = {
        ...data?.sendMessage.message,
        avAssets: [],
        replyCount: 0,
        ref: '',
        tags: [],
        userName: profile?.name,
        userProfilePhotoUrl: profile?.profilePhotoUrl,
        keyValueContent: [],
        block: { id: '' },
        room: { id: roomId },
        modifiedAt: new Date(),
        resolved: true,
        parent: { id: '', type: '' },
        reactionEmoji: '',
        AvAsset: {},
      };
      const multipleMessages = [...(cacheMessages?.multipleMessages || []), newMessage];
      cache.writeQuery({
        query: MultipleMessagesDocument,
        variables: {
          input: {
            status: MessageStatusEnum.Sent,
            roomId,
            limit: LIMITS.messages,
          },
        },
        data: {
          multipleMessages,
        },
      });
    },
  });

  const doCreateDraftMessage = useCallback(
    (strings: RichTextContentSegmentString[]) => {
      createDraftMessage({
        variables: {
          input: {
            type: MessageTypeEnum.Text,
            richTextContent: [
              {
                type: RichTextContentSegmentTypeEnum.Paragraph,
                strings: getParsedStrings(strings),
                order: 1,
              },
            ],
            roomId: roomId,
          },
        },
      });
    },
    [createDraftMessage, getParsedStrings, roomId]
  );

  const doUpdateMessage = useCallback(
    (strings: RichTextContentSegmentString[]) => {
      updateMessage({
        variables: {
          input: {
            id: draftId as string,
            richTextContent: [
              {
                type: RichTextContentSegmentTypeEnum.Paragraph,
                strings: getParsedStrings(strings),
                order: 1,
              },
            ],
          },
        },
      });
    },
    [draftId, getParsedStrings, updateMessage]
  );

  const clearMessageEditing = useCallback(() => {
    editingMessageIdVar('');
    isEditingMessageVar(false);
  }, []);

  const doSendMessage = useCallback(
    (editorState?: EditorState) => {
      if (isEditingMessage && editorState) {
        const strings = parseLexicalContentToLocal(editorState);
        updateMessage({
          variables: {
            input: {
              id: editingMessageId,
              richTextContent: [
                {
                  type: RichTextContentSegmentTypeEnum.Paragraph,
                  strings: getParsedStrings(strings),
                  order: 1,
                },
              ],
            },
          },
        });
        clearMessageEditing();
      } else {
        const isValidMessage = richTextContent.strings.length > 0;
        if (isValidMessage) {
          if (draftId) {
            sendMessage({
              variables: {
                id: draftId as string,
              },
              optimisticResponse: {
                sendMessage: {
                  message: {
                    __typename: 'Message',
                    id: uniqueId(),
                    sentAt: new Date(),
                    createdAt: new Date(),
                    userName: {
                      first: profile?.name.first as string,
                      last: profile?.name.last as string,
                    },
                    type: MessageTypeEnum.Text,
                    userProfilePhotoUrl: profile?.profilePhotoUrl,
                    status: MessageStatusEnum.Sent,
                    richTextContent: [
                      {
                        __typename: 'RichTextContentSegment',
                        id: uniqueId(),
                        type: RichTextContentSegmentTypeEnum.Paragraph,
                        strings: getParsedStringsForOptimistic(
                          richTextContent.strings
                        ) as RichTextContentSegmentString[],
                        order: 1,
                      },
                    ],
                  },
                },
              },
            });
          } else {
            // TODO: need to handle at backend
            doCreateDraftMessage(richTextContent.strings);
          }
        }
      }
    },
    [
      clearMessageEditing,
      doCreateDraftMessage,
      draftId,
      editingMessageId,
      getParsedStrings,
      getParsedStringsForOptimistic,
      isEditingMessage,
      profile?.name.first,
      profile?.name.last,
      profile?.profilePhotoUrl,
      richTextContent.strings,
      sendMessage,
      updateMessage,
    ]
  );

  const handleRichTextContent = useCallback(
    (input: RichTextContentSegment) => {
      setRichTextContent(input);
      const isValidMessage = input.strings.length > 0;
      const isTextRemoved =
        !isValidMessage && richTextContent.strings.length > input.strings.length;
      if (draftId) {
        if (isValidMessage || isTextRemoved) {
          doUpdateMessage(input.strings);
        }
        if (isTextRemoved) {
          setDraft(undefined);
        }
      } else {
        if (isValidMessage) {
          doCreateDraftMessage(input.strings);
        }
      }
    },
    [doCreateDraftMessage, doUpdateMessage, draftId, richTextContent.strings.length]
  );

  const handleChange = (editorState: EditorState) => {
    const strings = parseLexicalContentToLocal(editorState);
    if (strings.length > 0) {
      handleRichTextContent({
        strings,
        id: uniqueId(),
        type: RichTextContentSegmentTypeEnum.Paragraph,
        order: 1,
      });
    }
  };

  // eslint-disable-next-line react-hooks/exhaustive-deps
  const debouncedChangeHandler = useCallback(debounce(handleChange, INPUT_DEBOUNCE), [
    INPUT_DEBOUNCE,
    handleChange,
    debounce,
  ]);

  const borderRadius = isSmall ? 0 : 10;
  const borderWidth = isSmall ? 0 : 1;

  return (
    <Container
      overflow="hidden"
      borderRadius={borderRadius}
      borderColor="base-02"
      borderWidth={borderWidth}
      borderTopWidth={1}
    >
      <Visible if={isEditingMessage}>
        <ReferenceMessage variant="edit" message={message as MessageFragment} />
      </Visible>

      <Container>
        <Editor
          initialState={{}}
          draft={draft}
          message={message}
          handleSendMessage={doSendMessage}
          handleChange={debouncedChangeHandler}
        />
      </Container>
    </Container>
  );
};

export interface ChatInputProps {
  variant: RichTextVariant;
  message?: MessageFragment;
  roomId: string;
}
interface RichTextContentProps {
  id: string;
  strings: RichTextContentSegmentString[];
}
