import { useMemo } from 'react';

import { escapeForRegEx } from '@tiptap/core';
import CharacterCount from '@tiptap/extension-character-count';
import History from '@tiptap/extension-history';
import Mention from '@tiptap/extension-mention';
import Placeholder from '@tiptap/extension-placeholder';
import { Plugin, PluginKey } from '@tiptap/pm/state';
import { type EditorOptions, Extension, mergeAttributes } from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import { type SuggestionMatch, type Trigger } from '@tiptap/suggestion';

import { useAuth } from 'src/contexts/JWTContext';
import useFeatureFlag from 'src/hooks/useFeatureFlag';
import { USER_ALL } from 'src/pages/dashboard/Conversations/constants';
import { type Member } from 'src/pages/dashboard/Conversations/types';
import { sortMembersAlphabetically } from 'src/pages/dashboard/Conversations/utils';
import { FeatureFlags } from 'src/types/featureFlags';
import { hasFileToPaste, hasRichTextToPaste } from 'src/utils/attachments';

import { createMentionOptions } from 'src/components/RichTextEditor/mentions/mentionOptions';

const EnterHandler = Extension.create({
  name: 'enterHandler',
  addKeyboardShortcuts() {
    return {
      Enter: ({ editor }) => {
        const mentionState = editor.storage.mention?.query !== undefined;
        if (mentionState) {
          return false;
        }

        if (!navigator.onLine) {
          return true;
        }

        const editorElement = editor.view.dom;
        const form = editorElement.closest('form');

        if (form) {
          form.requestSubmit();
          return true;
        }

        return false;
      },
      'Shift-Enter': () => {
        return false;
      },
      Escape: ({ editor }) => {
        const mentionState = editor.storage.mention?.query !== undefined;
        if (mentionState) {
          return false;
        }

        return true;
      },
    };
  },
});

const TextPasteHandler = Extension.create({
  name: 'textPasteHandler',
  addOptions(): { handlePaste: ((event: ClipboardEvent) => void) | null } {
    return {
      handlePaste: null,
    };
  },
  addProseMirrorPlugins() {
    const { handlePaste } = this.options;
    return [
      new Plugin({
        key: new PluginKey('textPasteHandler'),
        props: {
          handlePaste(view, event) {
            const hasFile = hasFileToPaste(event);
            const hasRichText = hasRichTextToPaste(event);
            if (hasFile && !hasRichText) {
              if (handlePaste) {
                event.preventDefault();
                handlePaste(event);
                return true;
              }
              return false;
            }
            const text = event.clipboardData?.getData('text/plain');
            if (!text) return false;

            event.preventDefault();

            const { from, to } = view.state.selection;
            const { schema } = view.state;
            let tr = view.state.tr.delete(from, to);
            let pos = from;

            if (text.includes('\n')) {
              const lines = text.split('\n');
              lines.forEach((line: string, index: number) => {
                if (index > 0) {
                  const hardBreak = schema.nodes.hardBreak.create();
                  tr = tr.insert(pos, hardBreak);
                  pos += 1;
                }
                if (line) {
                  tr = tr.insert(pos, schema.text(line));
                  pos += line.length;
                }
              });
            } else {
              tr = tr.insert(pos, schema.text(text));
            }

            view.dispatch(tr);
            return true;
          },
        },
      }),
    ];
  },
});

const AutoScrollHandler = Extension.create({
  name: 'autoScrollHandler',
  addProseMirrorPlugins() {
    return [
      new Plugin({
        key: new PluginKey('autoScrollHandler'),
        view() {
          return {
            update(view, prevState) {
              const { selection } = view.state;
              const prevSelection = prevState.selection;

              if (!selection.eq(prevSelection)) {
                const { from } = selection;
                const coords = view.coordsAtPos(from);
                const editorRect = view.dom.getBoundingClientRect();
                if (
                  coords.top < editorRect.top ||
                  coords.bottom > editorRect.bottom
                ) {
                  const container = view.dom;
                  const targetScrollTop =
                    coords.top -
                    editorRect.top +
                    container.scrollTop -
                    editorRect.height / 2;

                  container.scrollTo({
                    top: Math.max(0, targetScrollTop),
                  });
                }
              }
            },
          };
        },
      }),
    ];
  },
});

const PROSEMIRROR_LEAF_TEXT_FALLBACK = '\0';

const findMentionSuggestionMatch = ({
  char,
  allowSpaces,
  allowedPrefixes,
  startOfLine,
  $position,
}: Trigger): SuggestionMatch => {
  const escapedChar = escapeForRegEx(char);
  const suffix = new RegExp(`\\s${escapedChar}$`);
  const prefix = startOfLine ? '^' : '';
  const regexp = allowSpaces
    ? new RegExp(
        `${prefix}${escapedChar}+(?:.*?(?=\\s${escapedChar}+|$))`,
        'gm',
      )
    : new RegExp(`${prefix}${escapedChar}+(?:[^\\s${escapedChar}]*)`, 'gm');
  const leadingTriggerRegex = new RegExp(`^${escapedChar}+`);
  const isCaretAfterMention = $position.nodeBefore?.type.name === 'mention';

  if (isCaretAfterMention) {
    return null;
  }

  const text = $position.parent.textBetween(
    0,
    $position.parentOffset,
    undefined,
    PROSEMIRROR_LEAF_TEXT_FALLBACK,
  );

  if (!text) {
    return null;
  }

  const textFrom = $position.pos - text.length;
  const match = Array.from(text.matchAll(regexp)).pop();

  if (!match || match.input === undefined || match.index === undefined) {
    return null;
  }

  const matchPrefix = match.input.slice(
    Math.max(0, match.index - 1),
    match.index,
  );
  const matchPrefixIsAllowed = new RegExp(
    `^[${allowedPrefixes?.join('')}${PROSEMIRROR_LEAF_TEXT_FALLBACK}]?$`,
  ).test(matchPrefix);

  if (allowedPrefixes !== null && !matchPrefixIsAllowed) {
    return null;
  }

  const matchedText = match[0];
  const activeTriggerOffset = Math.max(
    0,
    (matchedText.match(leadingTriggerRegex)?.[0].length ?? 1) - 1,
  );
  const activeText = matchedText.slice(activeTriggerOffset);
  const from = textFrom + match.index + activeTriggerOffset;
  let to = from + activeText.length;
  const localTo = match.index + matchedText.length;

  if (allowSpaces && suffix.test(text.slice(localTo - 1, localTo + 1))) {
    to += 1;
  }

  if (from < $position.pos && to >= $position.pos) {
    return {
      range: {
        from,
        to,
      },
      query: activeText.slice(char.length),
      text: activeText,
    };
  }

  return null;
};

type UseExtensionsOptions = {
  placeholder: string;
  members: Member[];
  maxLength: number;
  isGroupConversation: boolean;
  isPopper?: boolean | undefined;
  handlePaste?: (event: ClipboardEvent) => void;
};

const useExtensions = ({
  placeholder,
  members,
  maxLength,
  isPopper = false,
  handlePaste,
  isGroupConversation,
}: UseExtensionsOptions): EditorOptions['extensions'] => {
  const { user } = useAuth();
  const isEnableMentionAll = useFeatureFlag(FeatureFlags.CHATS_V2_MENTION_ALL);
  const filteredMembers = useMemo(() => {
    const membersWithoutCurrentUser = members.filter(
      member => member.id !== user?.id,
    );
    const sortedMembers = sortMembersAlphabetically(membersWithoutCurrentUser);
    return isEnableMentionAll ? [USER_ALL, ...sortedMembers] : sortedMembers;
  }, [isEnableMentionAll, members, user?.id]);

  return useMemo(() => {
    const extensions: EditorOptions['extensions'] = [
      StarterKit.configure({
        bold: false,
        italic: false,
        strike: false,
        code: false,
        codeBlock: false,
        blockquote: false,
        heading: false,
        horizontalRule: false,
        orderedList: false,
        bulletList: false,
        listItem: false,
        history: false,
      }),
      CharacterCount.configure({
        limit: maxLength,
      }),
      Placeholder.configure({
        placeholder,
      }),
      History,
    ];

    if (isGroupConversation) {
      extensions.push(
        Mention.configure({
          HTMLAttributes: {
            class: 'mention',
          },
          suggestion: {
            ...createMentionOptions(filteredMembers, 'top-start', isPopper),
            findSuggestionMatch: findMentionSuggestionMatch,
          },
          renderHTML({ options, node }) {
            return [
              'a',
              mergeAttributes({}, options.HTMLAttributes),
              `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`,
            ];
          },
          renderText({ node }) {
            const member = filteredMembers.find(
              m => m.id.toString() === node.attrs.id,
            );
            if (member) {
              return `<@${member.first_name} ${member.last_name}>`;
            }
            return `<@${node.attrs.label ?? node.attrs.id}>`;
          },
        }),
      );
    }
    extensions.push(EnterHandler);
    extensions.push(TextPasteHandler.configure({ handlePaste }));
    extensions.push(AutoScrollHandler);

    return extensions;
  }, [
    filteredMembers,
    handlePaste,
    isGroupConversation,
    isPopper,
    maxLength,
    placeholder,
  ]);
};

export default useExtensions;
