import {
  forwardRef,
  type ReactNode,
  useCallback,
  useEffect,
  useImperativeHandle,
  useMemo,
  useRef,
} from 'react';
import { Controller, useFormContext } from 'react-hook-form';

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 {
  type Content,
  type Editor,
  EditorContent,
  type EditorOptions,
  Extension,
  type JSONContent,
  mergeAttributes,
  useEditor,
} from '@tiptap/react';
import StarterKit from '@tiptap/starter-kit';
import Box from '@material-hu/mui/Box';
import { styled } from '@material-hu/mui/styles';

import useHuGoTheme from 'src/hooks/useHuGoTheme';
import { type TaggedUser } from 'src/types/user';

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

export type CommentRichTextInputRef = {
  editor: Editor | null;
};

export const buildEditorContent = (
  text: string,
  bodyAttributes: TaggedUser[] = [],
): JSONContent => {
  if (!text) return { type: 'doc', content: [{ type: 'paragraph' }] };

  const sortedMentions = [...bodyAttributes].sort((a, b) => a.start - b.start);
  const lines = text.split('\n');
  const paragraphs: JSONContent[] = [];
  let globalOffset = 0;

  for (const line of lines) {
    const lineEnd = globalOffset + line.length;
    const lineMentions = sortedMentions.filter(
      m => m.start >= globalOffset && m.start + m.length <= lineEnd,
    );

    const content: JSONContent[] = [];
    let pos = globalOffset;

    for (const mention of lineMentions) {
      if (mention.start > pos) {
        content.push({ type: 'text', text: text.slice(pos, mention.start) });
      }
      const mentionText = text.slice(
        mention.start,
        mention.start + mention.length,
      );
      const label = mentionText.startsWith('@')
        ? mentionText.slice(1)
        : mentionText;
      content.push({
        type: 'mention',
        attrs: { id: mention.type.user.id.toString(), label },
      });
      pos = mention.start + mention.length;
    }

    if (pos < lineEnd) {
      content.push({ type: 'text', text: text.slice(pos, lineEnd) });
    }

    paragraphs.push({
      type: 'paragraph',
      ...(content.length > 0 ? { content } : {}),
    });

    globalOffset = lineEnd + 1;
  }

  return { type: 'doc', content: paragraphs };
};

const EnterHandler = Extension.create<{
  onEnterSubmitRef: React.RefObject<(() => void) | null>;
  onEscapeKeyRef: React.RefObject<(() => void) | null>;
}>({
  name: 'enterHandler',
  addOptions() {
    return {
      onEnterSubmitRef: { current: null },
      onEscapeKeyRef: { current: null },
    };
  },
  addKeyboardShortcuts() {
    return {
      Enter: ({ editor }) => {
        if (editor.storage.mention?.query !== undefined) return false;
        const form = editor.view.dom.closest('form');
        if (form) {
          this.options.onEnterSubmitRef.current?.();
          form.requestSubmit();
          return true;
        }
        return false;
      },
      'Shift-Enter': () => false,
      Escape: ({ editor }) => {
        if (editor.storage.mention?.query !== undefined) return false;
        this.options.onEscapeKeyRef.current?.();
        return true;
      },
    };
  },
});

type CommentRichTextInputProps = {
  name: string;
  placeholder?: string;
  disabled?: boolean;
  onBlur?: () => void;
  onFocus?: () => void;
  endAdornment?: ReactNode;
  initialContent?: Content;
  onEnterSubmit?: () => void;
  onEscapeKey?: () => void;
  onReady?: () => void;
  autoFocus?: boolean;
  sx?: object;
};

const StyledContainer = styled(Box, {
  shouldForwardProp: prop =>
    !['isFocused', 'isEmpty', 'disabled'].includes(prop as string),
})<{
  isFocused?: boolean;
  isEmpty?: boolean;
  disabled?: boolean;
}>(({ theme, isFocused, isEmpty, disabled }) => ({
  display: 'flex',
  alignItems: 'center',
  width: '100%',
  border: '1px solid',
  borderRadius: `${theme.shape.borderRadius}px`,
  borderColor:
    (isFocused && theme.palette.ilustrations?.primaryIlus) ||
    ((isEmpty || disabled) && theme.palette.new.border.neutral.default) ||
    theme.palette.new.text.neutral.disabled,

  '&:hover': {
    borderColor: theme.palette.primary.main,
  },

  '& .tiptap': {
    ...theme.typography.body1,
    fontFamily: theme.typography.fontFamily,
    padding: '8px 12px',
    flex: 1,
    minHeight: '24px',
    maxHeight: '120px',
    overflowY: 'auto',

    '& p': {
      margin: 0,
    },

    '&:focus': {
      outline: 'none',
    },

    '& p.is-editor-empty::before': {
      color: theme.palette.text.disabled,
      content: 'attr(data-placeholder)',
      height: 0,
      float: 'left',
      pointerEvents: 'none',
    },

    '& .mention': {
      color: theme.palette.primary.main,
      textDecoration: 'underline',
    },
  },
}));

type UseCommentExtensionsOptions = {
  placeholder?: string;
  onEnterSubmitRef: React.RefObject<(() => void) | null>;
  onEscapeKeyRef: React.RefObject<(() => void) | null>;
};

const useCommentExtensions = ({
  placeholder,
  onEnterSubmitRef,
  onEscapeKeyRef,
}: UseCommentExtensionsOptions) =>
  useMemo(() => {
    const extensions: EditorOptions['extensions'] = [
      StarterKit.configure({
        horizontalRule: false,
        history: false,
        heading: false,
        bold: false,
        italic: false,
        strike: false,
        code: false,
        codeBlock: false,
        blockquote: false,
        bulletList: false,
        orderedList: false,
        listItem: false,
      }),
      CharacterCount.configure({ limit: 10000 }),
      Placeholder.configure({ placeholder }),
      Mention.configure({
        HTMLAttributes: { class: 'mention' },
        suggestion: createMentionOptions(),
        renderHTML({ options, node }) {
          return [
            'a',
            mergeAttributes(
              { href: getProfileDetailHref(node.attrs.id) },
              options.HTMLAttributes,
            ),
            `${options.suggestion.char}${node.attrs.label ?? node.attrs.id}`,
          ];
        },
      }),
      History,
      EnterHandler.configure({ onEnterSubmitRef, onEscapeKeyRef }),
    ];

    return extensions;
  }, [placeholder, onEnterSubmitRef, onEscapeKeyRef]);

const CommentRichTextInput = forwardRef<
  CommentRichTextInputRef,
  CommentRichTextInputProps
>(
  (
    {
      name,
      placeholder,
      disabled = false,
      onBlur,
      onFocus,
      endAdornment,
      initialContent = '',
      onEnterSubmit,
      onEscapeKey,
      onReady,
      autoFocus = false,
      sx,
    },
    ref,
  ) => {
    const HugoThemeProvider = useHuGoTheme();
    const onEnterSubmitRef = useRef<(() => void) | null>(null);
    const onEscapeKeyRef = useRef<(() => void) | null>(null);
    const onReadyRef = useRef<(() => void) | null>(null);
    onEnterSubmitRef.current = onEnterSubmit ?? null;
    onEscapeKeyRef.current = onEscapeKey ?? null;
    onReadyRef.current = onReady ?? null;

    const extensions = useCommentExtensions({
      placeholder,
      onEnterSubmitRef,
      onEscapeKeyRef,
    });
    const form = useFormContext();

    const editor = useEditor({
      extensions,
      content: initialContent,
      editable: !disabled,
      autofocus: autoFocus,
    });

    useImperativeHandle(ref, () => ({ editor }), [editor]);

    useEffect(() => {
      if (editor) {
        onReadyRef.current?.();
      }
    }, [editor]);

    useEffect(() => {
      if (editor) {
        editor.setEditable(!disabled);
      }
    }, [disabled, editor]);

    useEffect(() => {
      if (!editor) return;

      const normalizedInitialContent = initialContent || '';
      const currentEditorContent = editor.isEmpty ? '' : editor.getHTML();

      if (normalizedInitialContent === currentEditorContent) {
        return;
      }

      editor.commands.setContent(normalizedInitialContent);
    }, [editor, initialContent]);

    const wasDirtyRef = useRef(false);

    useEffect(() => {
      const { isDirty } = form.formState;
      if (wasDirtyRef.current && !isDirty && editor && !editor.isEmpty) {
        editor.commands.setContent(initialContent || '');
      }
      wasDirtyRef.current = isDirty;
    }, [form.formState.isDirty, editor, initialContent]);

    const onChangeRef = useRef<((value: string) => void) | null>(null);

    const handleControllerChange = useCallback(
      (onChange: (value: string) => void) => {
        onChangeRef.current = onChange;
      },
      [],
    );

    const initialSyncDoneRef = useRef(false);

    useEffect(() => {
      if (!editor || editor.isEmpty || initialSyncDoneRef.current) return;
      initialSyncDoneRef.current = true;
      onChangeRef.current?.(editor.getHTML());
    }, [editor]);

    useEffect(() => {
      if (!editor) return;

      const handleUpdate = () => {
        const value = editor.isEmpty ? '' : editor.getHTML();
        onChangeRef.current?.(value);
      };
      const handleBlur = () => onBlur?.();
      const handleFocus = () => onFocus?.();

      editor.on('update', handleUpdate);
      editor.on('blur', handleBlur);
      editor.on('focus', handleFocus);

      return () => {
        editor.off('update', handleUpdate);
        editor.off('blur', handleBlur);
        editor.off('focus', handleFocus);
      };
    }, [editor, onBlur, onFocus]);

    return (
      <HugoThemeProvider>
        <Controller
          name={name}
          render={({ field: { onChange } }) => {
            handleControllerChange(onChange);

            return (
              <StyledContainer
                isEmpty={editor?.isEmpty}
                isFocused={editor?.isFocused}
                disabled={disabled}
                sx={sx}
              >
                <EditorContent
                  editor={editor}
                  style={{ flex: 1 }}
                />
                {endAdornment && (
                  <Box sx={{ display: 'flex', alignItems: 'center', pr: 1 }}>
                    {endAdornment}
                  </Box>
                )}
              </StyledContainer>
            );
          }}
        />
      </HugoThemeProvider>
    );
  },
);

CommentRichTextInput.displayName = 'CommentRichTextInput';

export default CommentRichTextInput;
