import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import { parseEditorJsonToText, plainTextToTipTapDoc } from '@/lib/tiptap'
import { cn } from '@/lib/utils'
import customEmojiService from '@/services/custom-emoji.service'
import postEditorCache from '@/services/post-editor-cache.service'
import { TEmoji } from '@/types'
import Document from '@tiptap/extension-document'
import { HardBreak } from '@tiptap/extension-hard-break'
import History from '@tiptap/extension-history'
import Paragraph from '@tiptap/extension-paragraph'
import Placeholder from '@tiptap/extension-placeholder'
import Text from '@tiptap/extension-text'
import { TextSelection } from '@tiptap/pm/state'
import { Editor, EditorContent, useEditor } from '@tiptap/react'
import { Event } from 'nostr-tools'
import { useScreenSizeOptional } from '@/providers/ScreenSizeProvider'
import {
  Dispatch,
  forwardRef,
  SetStateAction,
  useEffect,
  useImperativeHandle,
  useMemo,
  useRef,
  useState
} from 'react'
import { useTranslation } from 'react-i18next'
import { ClipboardAndDropHandler } from './ClipboardAndDropHandler'
import Emoji from './Emoji'
import emojiSuggestion from './Emoji/suggestion'
import Mention from './Mention'
import mentionSuggestion from './Mention/suggestion'
import Preview from './Preview'
import { HighlightData } from '../HighlightEditor'
import { getKindDescription } from '@/lib/kind-description'

export type TPostTextareaHandle = {
  appendText: (text: string, addNewline?: boolean) => void
  insertText: (text: string) => void
  insertEmoji: (emoji: string | TEmoji) => void
  clear: () => void
  /** Re-read `postEditorCache` / `defaultContent` into TipTap (dialog reopened; initial `content` only runs once). */
  syncFromPostCache: () => void
  getText: () => string
  /** Replace editor from plain `content` (e.g. advanced lab). Syncs TipTap JSON cache and parent `text`. */
  setDocumentFromPlainText: (plain: string) => void
}

const PostTextarea = forwardRef<
  TPostTextareaHandle,
  {
    text: string
    setText: Dispatch<SetStateAction<string>>
    defaultContent?: string
    parentEvent?: Event
    onSubmit?: () => void
    className?: string
    onUploadStart?: (file: File, cancel: () => void) => void
    onUploadProgress?: (file: File, progress: number) => void
    onUploadEnd?: (file: File) => void
    onUploadSuccess?: (result: {
      url: string
      tags: string[][]
      file: File
      urlAlreadyInEditor?: boolean
    }) => void
    onUploadCompressPhase?: (file: File, phase: 'compressing' | 'uploading') => void
    onUploadCompressProgress?: (file: File, percent: number) => void
    kind?: number
    highlightData?: HighlightData
    pollCreateData?: import('@/types').TPollCreateData
    headerActions?: React.ReactNode
    mediaImetaTags?: string[][]
    mediaUrl?: string
    articleMetadata?: {
      title?: string
      summary?: string
      image?: string
      dTag?: string
      topics?: string[]
      affectedKinds?: number[]
    }
    musicTrackMetadata?: {
      dTag?: string
      title?: string
      audioUrl?: string
      artist?: string
      imageUrl?: string
      album?: string
      durationSec?: number
      format?: string
      language?: string
      genres?: string[]
    }
    extraPreviewTags?: string[][]
    addClientTag?: boolean
  }
>(
  (
    {
      text = '',
      setText,
      defaultContent,
      parentEvent,
      onSubmit,
      className,
      onUploadStart,
      onUploadProgress,
      onUploadEnd,
      onUploadSuccess,
      onUploadCompressPhase,
      onUploadCompressProgress,
      kind = 1,
      highlightData,
      pollCreateData,
      headerActions,
      mediaImetaTags,
      mediaUrl,
      articleMetadata,
      musicTrackMetadata,
      extraPreviewTags,
      addClientTag = true
    },
    ref
  ) => {
    const { t } = useTranslation()
    const isSmallScreen = useScreenSizeOptional()?.isSmallScreen ?? false
    const onUploadSuccessRef = useRef(onUploadSuccess)
    onUploadSuccessRef.current = onUploadSuccess
    const onUploadCompressPhaseRef = useRef(onUploadCompressPhase)
    onUploadCompressPhaseRef.current = onUploadCompressPhase
    const onUploadCompressProgressRef = useRef(onUploadCompressProgress)
    onUploadCompressProgressRef.current = onUploadCompressProgress
    const onUploadStartRef = useRef(onUploadStart)
    onUploadStartRef.current = onUploadStart
    const onUploadEndRef = useRef(onUploadEnd)
    onUploadEndRef.current = onUploadEnd
    const onUploadProgressRef = useRef(onUploadProgress)
    onUploadProgressRef.current = onUploadProgress
    const onSubmitRef = useRef(onSubmit)
    onSubmitRef.current = onSubmit
    const [activeTab, setActiveTab] = useState('edit')
    const editorRef = useRef<Editor | null>(null)

    const kindDescription = useMemo(() => getKindDescription(kind), [kind])

    const placeholderText = useMemo(
      () => t('Write something...') + ' (' + t('Paste or drop media files to upload') + ')',
      [t]
    )

    // Extension instances must be stable — recreating them each render makes useEditor destroy/recreate
    // the editor (blank composer in reply dialog).
    const extensions = useMemo(
      () => [
        Document,
        Paragraph,
        Text,
        History,
        HardBreak,
        Placeholder.configure({ placeholder: placeholderText }),
        Emoji.configure({ suggestion: emojiSuggestion }),
        Mention.configure({ suggestion: mentionSuggestion }),
        ClipboardAndDropHandler.configure({
          onUploadStart: (file, cancel) => onUploadStartRef.current?.(file, cancel),
          onUploadEnd: (file) => onUploadEndRef.current?.(file),
          onUploadProgress: (file, p) => onUploadProgressRef.current?.(file, p),
          onUploadSuccess: (result) => onUploadSuccessRef.current?.(result),
          onUploadCompressPhase: (file, phase) =>
            onUploadCompressPhaseRef.current?.(file, phase),
          onUploadCompressProgress: (file, pct) =>
            onUploadCompressProgressRef.current?.(file, pct)
        })
      ],
      [placeholderText]
    )

    const editorSurfaceClass = useMemo(
      () =>
        cn(
          'border rounded-lg p-3 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
          className
        ),
      [className]
    )

    const editor = useEditor({
      // TipTap + Radix Dialog/Tabs: defer init so React 18 does not warn about flushSync in a lifecycle.
      immediatelyRender: false,
      extensions,
      editorProps: {
        attributes: {
          class: editorSurfaceClass
        },
        handleKeyDown: (_view, event) => {
          if ((event.ctrlKey || event.metaKey) && event.key === 'Enter') {
            event.preventDefault()
            onSubmitRef.current?.()
            return true
          }
          return false
        },
        clipboardTextSerializer(content) {
          return parseEditorJsonToText(content.toJSON())
        }
      },
      content: postEditorCache.getPostContentCache({ kind, defaultContent, parentEvent }),
      onUpdate(props) {
        setText(parseEditorJsonToText(props.editor.getJSON()))
        postEditorCache.setPostContentCache({ kind, defaultContent, parentEvent }, props.editor.getJSON())
      },
      onCreate(props) {
        setText(parseEditorJsonToText(props.editor.getJSON()))
      }
    })

    editorRef.current = editor

    useEffect(() => {
      if (!editor) return
      editor.setOptions({
        editorProps: {
          ...editor.options.editorProps,
          attributes: { class: editorSurfaceClass }
        }
      })
    }, [editor, editorSurfaceClass])

    useEffect(() => {
      if (!editor || !isSmallScreen) return
      const scrollEditorIntoView = () => {
        requestAnimationFrame(() => {
          editor.view.dom.scrollIntoView({ block: 'nearest', inline: 'nearest' })
        })
      }
      editor.on('focus', scrollEditorIntoView)
      return () => {
        editor.off('focus', scrollEditorIntoView)
      }
    }, [editor, isSmallScreen])

    useImperativeHandle(ref, () => ({
      appendText: (text: string, addNewline = false) => {
        const ed = editorRef.current
        if (ed) {
          let chain = ed
            .chain()
            .focus()
            .command(({ tr, dispatch }) => {
              if (dispatch) {
                const endPos = tr.doc.content.size
                const selection = TextSelection.create(tr.doc, endPos)
                tr.setSelection(selection)
                dispatch(tr)
              }
              return true
            })
            .insertContent(text)
          if (addNewline) {
            chain = chain.setHardBreak()
          }
          chain.run()
        }
      },
      insertText: (text: string) => {
        const editor = editorRef.current
        if (editor) {
          editor.chain().focus().insertContent(text).run()
        }
      },
      insertEmoji: (emoji: string | TEmoji) => {
        const editor = editorRef.current
        if (editor) {
          if (typeof emoji === 'string') {
            editor.chain().insertContent(emoji).run()
          } else {
            const emojiNode = editor.schema.nodes.emoji.create({
              name: customEmojiService.getEmojiId(emoji)
            })
            editor.chain().insertContent(emojiNode).insertContent(' ').run()
          }
        }
      },
      clear: () => {
        const editor = editorRef.current
        if (editor) {
          // Clear the editor content and reset to empty document
          editor.chain().clearContent().run()
          // Also clear the cache
          postEditorCache.setPostContentCache({ kind, defaultContent, parentEvent }, editor.getJSON())
          setText('')
        }
      },
      syncFromPostCache: () => {
        const editor = editorRef.current
        if (!editor) return
        const next = postEditorCache.getPostContentCache({ kind, defaultContent, parentEvent })
        if (next === undefined) return
        editor.chain().setContent(next).run()
        const json = editor.getJSON()
        setText(parseEditorJsonToText(json))
        postEditorCache.setPostContentCache({ kind, defaultContent, parentEvent }, json)
      },
      getText: () => {
        const editor = editorRef.current
        if (editor) {
          // Must match `onUpdate` / `clipboardTextSerializer` / `setDocumentFromPlainText` follow-up.
          // TipTap's `editor.getText()` uses a multi-line block separator (e.g. `\n\n`), which does not
          // round-trip with `plainTextToTipTapDoc` (one paragraph per `\n`) and inflates blank lines.
          return parseEditorJsonToText(editor.getJSON())
        }
        return ''
      },
      setDocumentFromPlainText: (plain: string) => {
        const editor = editorRef.current
        if (!editor) return
        const json = plainTextToTipTapDoc(plain)
        editor.chain().setContent(json).run()
        postEditorCache.setPostContentCache({ kind, defaultContent, parentEvent }, editor.getJSON())
        setText(parseEditorJsonToText(editor.getJSON()))
      }
    }))

    const editorShellClass = cn(
      'border rounded-lg p-3 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',
      className
    )

    return (
      <Tabs value={activeTab} onValueChange={setActiveTab} className="space-y-2">
        <div className="flex min-w-0 flex-col gap-2">
          <TabsList className="w-auto shrink-0 justify-start">
            <TabsTrigger value="edit" title={t('Edit')}>
              {t('Edit')}
            </TabsTrigger>
            <TabsTrigger value="preview" title={t('Preview')}>
              {t('Preview')}
            </TabsTrigger>
          </TabsList>
          {headerActions ? (
            <div className="flex min-w-0 flex-nowrap items-center justify-end gap-1 overflow-x-auto overscroll-x-contain">
              {headerActions}
            </div>
          ) : null}
        </div>
        <TabsContent
          value="edit"
          forceMount
          className="mt-0 data-[state=inactive]:hidden focus-visible:ring-0 focus-visible:ring-offset-0"
        >
          {editor ? (
            <EditorContent className="tiptap" editor={editor} />
          ) : (
            <div
              className={cn(editorShellClass, 'text-muted-foreground')}
              aria-hidden
            >
              {placeholderText}
            </div>
          )}
        </TabsContent>
        <TabsContent
          value="preview"
          className="mt-0 data-[state=inactive]:hidden focus-visible:ring-0 focus-visible:ring-offset-0"
        >
          <div className="space-y-2">
            <div className="text-xs text-muted-foreground">
              kind {kindDescription.number}: {kindDescription.description}
            </div>
            <Preview
              content={text}
              className={className}
              kind={kind}
              highlightData={highlightData}
              pollCreateData={pollCreateData}
              mediaImetaTags={mediaImetaTags}
              mediaUrl={mediaUrl}
              articleMetadata={articleMetadata}
              musicTrackMetadata={musicTrackMetadata}
              extraPreviewTags={extraPreviewTags}
              addClientTag={addClientTag}
            />
          </div>
        </TabsContent>
      </Tabs>
    )
  }
)
PostTextarea.displayName = 'PostTextarea'
export default PostTextarea
