import { Editor, isTextSelection } from '@tiptap/core'
import { BubbleMenu } from '@tiptap/extension-bubble-menu'
import { CharacterCount } from '@tiptap/extension-character-count'
import { FloatingMenu } from '@tiptap/extension-floating-menu'
import { Link } from '@tiptap/extension-link'
import { Placeholder } from '@tiptap/extension-placeholder'
import { createEffect, createSignal, onCleanup } from 'solid-js'
import { createTiptapEditor } from 'solid-tiptap'
import { useSnackbar } from '~/context/ui'
import { base, custom, extended } from '~/lib/editorExtensions'
import { handleClipboardPaste } from '~/lib/handleImageUpload'
import { useEditorContext } from '../../context/editor'
import { useLocalize } from '../../context/localize'
import { useSession } from '../../context/session'
import { BlockquoteBubbleMenu } from './Toolbar/BlockquoteBubbleMenu'
import { EditorFloatingMenu } from './Toolbar/EditorFloatingMenu'
import { FigureBubbleMenu } from './Toolbar/FigureBubbleMenu'
import { FullBubbleMenu } from './Toolbar/FullBubbleMenu'
import { IncutBubbleMenu } from './Toolbar/IncutBubbleMenu'
import { ArticleNode } from './extensions/Article'
import { TrailingNode } from './extensions/TrailingNode'

import './Editor.module.scss'

type Props = {
  shoutId: number
  initialContent?: string
  onChange: (text: string) => void
}

export const EditorComponent = (props: Props) => {
  const { t } = useLocalize()
  const { session } = useSession()
  const { showSnackbar } = useSnackbar()
  const { countWords, setEditing } = useEditorContext()
  const [isCommonMarkup, setIsCommonMarkup] = createSignal(false)
  const [shouldShowTextBubbleMenu, setShouldShowTextBubbleMenu] = createSignal(false)
  const [editorElRef, setEditorElRef] = createSignal<HTMLElement | undefined>()
  const [incutBubbleMenuRef, setIncutBubbleMenuRef] = createSignal<HTMLDivElement | undefined>()
  const [figureBubbleMenuRef, setFigureBubbleMenuRef] = createSignal<HTMLDivElement | undefined>()
  const [blockquoteBubbleMenuRef, setBlockquoteBubbleMenuRef] = createSignal<HTMLDivElement | undefined>()
  const [floatingMenuRef, setFloatingMenuRef] = createSignal<HTMLDivElement | undefined>()
  const [textBubbleMenuRef, setFullBubbleMenuRef] = createSignal<HTMLDivElement | undefined>()

  const editor = createTiptapEditor(() => ({
    element: editorElRef()!,
    editorProps: {
      attributes: {
        class: 'articleEditor'
      },
      transformPastedHTML(html) {
        return html.replaceAll(/<img.*?>/g, '')
      },
      handlePaste: () => {
        showSnackbar({ body: t('Uploading image') })
        handleClipboardPaste(editor(), session()?.access_token || '').then(() => false)
        return false
      }
    },
    extensions: [
      ...base,
      ...custom,
      ...extended,
      Placeholder.configure({
        placeholder: t('Add a link or click plus to embed media')
      }),
      CharacterCount.configure(), // https://github.com/ueberdosis/tiptap/issues/2589#issuecomment-1093084689
      BubbleMenu.configure({
        pluginKey: 'textBubbleMenu',
        element: textBubbleMenuRef()!,
        shouldShow: ({ editor: e, view, state: { doc, selection }, from, to }) => {
          const isEmptyTextBlock = doc.textBetween(from, to).length === 0 && isTextSelection(selection)
          if (isEmptyTextBlock) {
            e.chain().focus().removeTextWrap({ class: 'highlight-fake-selection' }).run()
          }
          setIsCommonMarkup(e.isActive('figcaption'))
          const result =
            (view.hasFocus() &&
              !selection.empty &&
              !isEmptyTextBlock &&
              !e.isActive('image') &&
              !e.isActive('figure')) ||
            e.isActive('footnote') ||
            (e.isActive('figcaption') && !selection.empty)
          setShouldShowTextBubbleMenu(result)
          return result
        },
        tippyOptions: {
          sticky: true
        }
      }),
      BubbleMenu.configure({
        pluginKey: 'blockquoteBubbleMenu',
        element: blockquoteBubbleMenuRef()!,
        shouldShow: ({ editor: e, state }) => {
          const { selection } = state
          const { empty } = selection
          return empty && e.isActive('blockquote')
        },
        tippyOptions: {
          offset: [0, 0],
          placement: 'top',
          getReferenceClientRect: (): DOMRect => {
            const selectedElement = editor()?.view.dom.querySelector('.has-focus') as HTMLElement | null
            if (selectedElement) {
              return selectedElement.getBoundingClientRect()
            }
            return new DOMRect()
          }
        }
      }),
      BubbleMenu.configure({
        pluginKey: 'incutBubbleMenu',
        element: incutBubbleMenuRef()!,
        shouldShow: ({ editor: e, state }) => {
          const { selection } = state
          const { empty } = selection
          return empty && e.isActive('article')
        },
        tippyOptions: {
          offset: [0, -16],
          placement: 'top',
          getReferenceClientRect: (): DOMRect => {
            const selectedElement = editor()?.view.dom.querySelector('.has-focus') as HTMLElement | null
            if (selectedElement) {
              return selectedElement.getBoundingClientRect()
            }
            return new DOMRect()
          }
        }
      }),
      BubbleMenu.configure({
        pluginKey: 'imageBubbleMenu',
        element: figureBubbleMenuRef()!,
        shouldShow: ({ editor: e, view }) => {
          return view.hasFocus() && e.isActive('image')
        }
      }),
      FloatingMenu.configure({
        tippyOptions: {
          placement: 'left'
        },
        element: floatingMenuRef()!
      }),
      TrailingNode,
      ArticleNode
    ],
    enablePasteRules: [Link],
    content: props.initialContent || null,
    onTransaction: ({ editor: e, transaction }) => {
      if (transaction.docChanged) {
        const html = e.getHTML()
        html && props.onChange(html)
        const wordCount: number = e.storage.characterCount.words()
        const charsCount: number = e.storage.characterCount.characters()
        charsCount && countWords({ words: wordCount, characters: charsCount })
      }
    }
  }))

  // store tiptap editor in context provider's signal to use it in Panel
  createEffect(() => setEditing(editor() || undefined))

  onCleanup(() => {
    editor()?.destroy()
  })

  return (
    <>
      <div class="row">
        <div class="col-md-5" />
        <div class="col-md-12">
          <div ref={setEditorElRef} id="editorBody" />
        </div>
      </div>

      <FullBubbleMenu
        editor={editor}
        ref={setFullBubbleMenuRef}
        shouldShow={shouldShowTextBubbleMenu}
        isCommonMarkup={isCommonMarkup()}
      />
      <BlockquoteBubbleMenu editor={editor() as Editor} ref={setBlockquoteBubbleMenuRef} />
      <FigureBubbleMenu editor={editor() as Editor} ref={setFigureBubbleMenuRef} />
      <IncutBubbleMenu editor={editor() as Editor} ref={setIncutBubbleMenuRef} />
      <EditorFloatingMenu editor={editor() as Editor} ref={setFloatingMenuRef} />
    </>
  )
}
