'use client'

import type { ReactNode } from 'react'
import type { UserProfile } from '@/contract/console/workflow-comment'
import { Avatar } from '@langgenius/dify-ui/avatar'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { RiArrowUpLine, RiAtLine, RiLoader2Line } from '@remixicon/react'
import {
  forwardRef,
  memo,
  useCallback,
  useEffect,
  useImperativeHandle,
  useLayoutEffect,
  useMemo,
  useRef,
  useState,
} from 'react'
import { createPortal } from 'react-dom'
import { useTranslation } from 'react-i18next'
import Textarea from 'react-textarea-autosize'
import EnterKey from '@/app/components/base/icons/src/public/common/EnterKey'
import { useParams } from '@/next/navigation'
import { consoleClient } from '@/service/client'
import { useStore, useWorkflowStore } from '../store'

type MentionInputProps = {
  value: string
  onChange: (value: string) => void
  onSubmit: (content: string, mentionedUserIds: string[]) => void
  onCancel?: () => void
  placeholder?: string
  disabled?: boolean
  loading?: boolean
  className?: string
  isEditing?: boolean
  autoFocus?: boolean
}

const EMPTY_USERS: UserProfile[] = []

const MentionInputInner = forwardRef<HTMLTextAreaElement, MentionInputProps>(({
  value,
  onChange,
  onSubmit,
  onCancel,
  placeholder,
  disabled = false,
  loading = false,
  className,
  isEditing = false,
  autoFocus = false,
}, forwardedRef) => {
  const params = useParams()
  const { t } = useTranslation()
  const appId = params.appId as string
  const textareaRef = useRef<HTMLTextAreaElement>(null)
  const highlightContentRef = useRef<HTMLDivElement>(null)
  const actionContainerRef = useRef<HTMLDivElement | null>(null)
  const actionRightRef = useRef<HTMLDivElement | null>(null)
  const baseTextareaHeightRef = useRef<number | null>(null)

  // Expose textarea ref to parent component
  useImperativeHandle(forwardedRef, () => textareaRef.current!, [])

  const workflowStore = useWorkflowStore()
  const mentionUsersFromStore = useStore(state => (
    appId ? state.mentionableUsersCache[appId] : undefined
  ))
  const mentionUsers = useMemo(() => mentionUsersFromStore ?? EMPTY_USERS, [mentionUsersFromStore])

  const [showMentionDropdown, setShowMentionDropdown] = useState(false)
  const [mentionQuery, setMentionQuery] = useState('')
  const [mentionPosition, setMentionPosition] = useState(0)
  const [selectedMentionIndex, setSelectedMentionIndex] = useState(0)
  const [mentionedUserIds, setMentionedUserIds] = useState<string[]>([])
  const resolvedPlaceholder = placeholder ?? t('comments.placeholder.add', { ns: 'workflow' })
  const BASE_PADDING = 4
  const [shouldReserveButtonGap, setShouldReserveButtonGap] = useState(isEditing)
  const [shouldReserveHorizontalSpace, setShouldReserveHorizontalSpace] = useState(() => !isEditing)
  const [paddingRight, setPaddingRight] = useState(() => BASE_PADDING + (isEditing ? 0 : 48))
  const [paddingBottom, setPaddingBottom] = useState(() => BASE_PADDING + (isEditing ? 32 : 0))

  const mentionNameList = useMemo(() => {
    const names = mentionUsers
      .map(user => user.name?.trim())
      .filter((name): name is string => Boolean(name))

    const uniqueNames = Array.from(new Set(names))
    uniqueNames.sort((a, b) => b.length - a.length)
    return uniqueNames
  }, [mentionUsers])

  const highlightedValue = useMemo<ReactNode>(() => {
    if (!value)
      return ''

    if (mentionNameList.length === 0)
      return value

    const segments: ReactNode[] = []
    let cursor = 0
    let hasMention = false

    while (cursor < value.length) {
      let nextMatchStart = -1
      let matchedName = ''

      for (const name of mentionNameList) {
        const searchStart = value.indexOf(`@${name}`, cursor)
        if (searchStart === -1)
          continue

        const previousChar = searchStart > 0 ? value[searchStart - 1] : ''
        if (searchStart > 0 && !/\s/.test(previousChar!))
          continue

        if (
          nextMatchStart === -1
          || searchStart < nextMatchStart
          || (searchStart === nextMatchStart && name.length > matchedName.length)
        ) {
          nextMatchStart = searchStart
          matchedName = name
        }
      }

      if (nextMatchStart === -1)
        break

      if (nextMatchStart > cursor)
        segments.push(<span key={`text-${cursor}`}>{value.slice(cursor, nextMatchStart)}</span>)

      const mentionEnd = nextMatchStart + matchedName.length + 1
      segments.push(
        <span key={`mention-${nextMatchStart}`} className="text-primary-600">
          {value.slice(nextMatchStart, mentionEnd)}
        </span>,
      )

      hasMention = true
      cursor = mentionEnd
    }

    if (!hasMention)
      return value

    if (cursor < value.length)
      segments.push(<span key={`text-${cursor}`}>{value.slice(cursor)}</span>)

    return segments
  }, [value, mentionNameList])

  const loadMentionableUsers = useCallback(async () => {
    if (!appId)
      return

    const state = workflowStore.getState()
    if (state.mentionableUsersCache[appId] !== undefined)
      return

    if (state.mentionableUsersLoading[appId])
      return

    state.setMentionableUsersLoading(appId, true)
    try {
      const response = await consoleClient.workflowComments.mentionUsers({
        params: { appId },
      })
      workflowStore.getState().setMentionableUsersCache(appId, response.users)
    }
    catch (error) {
      console.error('Failed to load mentionable users:', error)
    }
    finally {
      workflowStore.getState().setMentionableUsersLoading(appId, false)
    }
  }, [appId, workflowStore])

  useEffect(() => {
    loadMentionableUsers()
  }, [loadMentionableUsers])
  const syncHighlightScroll = useCallback(() => {
    const textarea = textareaRef.current
    const highlightContent = highlightContentRef.current
    if (!textarea || !highlightContent)
      return

    const { scrollTop, scrollLeft } = textarea
    highlightContent.style.transform = `translate(${-scrollLeft}px, ${-scrollTop}px)`
  }, [])

  const evaluateContentLayout = useCallback(() => {
    const textarea = textareaRef.current
    if (!textarea)
      return

    const extraBottom = Math.max(0, paddingBottom - BASE_PADDING)
    const effectiveClientHeight = textarea.clientHeight - extraBottom

    if (baseTextareaHeightRef.current === null)
      baseTextareaHeightRef.current = effectiveClientHeight

    const baseHeight = baseTextareaHeightRef.current ?? effectiveClientHeight
    const hasMultiline = effectiveClientHeight > baseHeight + 1
    const shouldReserveVertical = isEditing ? true : hasMultiline

    setShouldReserveButtonGap(shouldReserveVertical)
    setShouldReserveHorizontalSpace(!hasMultiline)
  }, [isEditing, paddingBottom])

  const updateLayoutPadding = useCallback(() => {
    const actionEl = actionContainerRef.current
    const rect = actionEl?.getBoundingClientRect()
    const rightRect = actionRightRef.current?.getBoundingClientRect()
    let actionWidth = 0
    if (rightRect)
      actionWidth = Math.ceil(rightRect.width)
    else if (rect)
      actionWidth = Math.ceil(rect.width)

    const actionHeight = rect ? Math.ceil(rect.height) : 0
    const fallbackWidth = Math.max(0, paddingRight - BASE_PADDING)
    const fallbackHeight = Math.max(0, paddingBottom - BASE_PADDING)
    const effectiveWidth = actionWidth > 0 ? actionWidth : fallbackWidth
    const effectiveHeight = actionHeight > 0 ? actionHeight : fallbackHeight

    const nextRight = BASE_PADDING + (shouldReserveHorizontalSpace ? effectiveWidth : 0)
    const nextBottom = BASE_PADDING + (shouldReserveButtonGap ? effectiveHeight : 0)

    setPaddingRight(prev => (prev === nextRight ? prev : nextRight))
    setPaddingBottom(prev => (prev === nextBottom ? prev : nextBottom))
  }, [shouldReserveButtonGap, shouldReserveHorizontalSpace, paddingRight, paddingBottom])

  const setActionContainerRef = useCallback((node: HTMLDivElement | null) => {
    actionContainerRef.current = node

    if (!isEditing)
      actionRightRef.current = node
    else if (!node)
      actionRightRef.current = null

    if (node && typeof window !== 'undefined')
      window.requestAnimationFrame(() => updateLayoutPadding())
  }, [isEditing, updateLayoutPadding])

  const setActionRightRef = useCallback((node: HTMLDivElement | null) => {
    actionRightRef.current = node

    if (node && typeof window !== 'undefined')
      window.requestAnimationFrame(() => updateLayoutPadding())
  }, [updateLayoutPadding])

  useLayoutEffect(() => {
    syncHighlightScroll()
  }, [value, syncHighlightScroll])

  useLayoutEffect(() => {
    Promise.resolve().then(() => {
      evaluateContentLayout()
    })
  }, [value, evaluateContentLayout])

  useLayoutEffect(() => {
    Promise.resolve().then(() => {
      updateLayoutPadding()
    })
  }, [updateLayoutPadding, isEditing, shouldReserveButtonGap])

  useEffect(() => {
    const handleResize = () => {
      evaluateContentLayout()
      updateLayoutPadding()
    }

    window.addEventListener('resize', handleResize)
    return () => window.removeEventListener('resize', handleResize)
  }, [evaluateContentLayout, updateLayoutPadding])

  useEffect(() => {
    Promise.resolve().then(() => {
      baseTextareaHeightRef.current = null
      evaluateContentLayout()
      setShouldReserveHorizontalSpace(!isEditing)
    })
  }, [isEditing, evaluateContentLayout])

  const filteredMentionUsers = useMemo(() => {
    if (!mentionQuery)
      return mentionUsers
    return mentionUsers.filter(user =>
      user.name.toLowerCase().includes(mentionQuery.toLowerCase())
      || user.email.toLowerCase().includes(mentionQuery.toLowerCase()),
    )
  }, [mentionUsers, mentionQuery])

  const shouldDisableMentionButton = useMemo(() => {
    if (showMentionDropdown)
      return true

    const textarea = textareaRef.current
    if (!textarea)
      return false

    const cursorPosition = textarea.selectionStart || 0
    const textBeforeCursor = value.slice(0, cursorPosition)
    return /@\w*$/.test(textBeforeCursor)
  }, [showMentionDropdown, value])

  const dropdownPosition = useMemo(() => {
    if (!showMentionDropdown || !textareaRef.current)
      return { x: 0, y: 0, placement: 'bottom' as const }

    const textareaRect = textareaRef.current.getBoundingClientRect()
    const dropdownHeight = 160 // max-h-40 = 10rem = 160px
    const viewportHeight = window.innerHeight
    const spaceBelow = viewportHeight - textareaRect.bottom
    const spaceAbove = textareaRect.top

    const shouldPlaceAbove = spaceBelow < dropdownHeight && spaceAbove > spaceBelow

    return {
      x: textareaRect.left,
      y: shouldPlaceAbove ? textareaRect.top - 4 : textareaRect.bottom + 4,
      placement: shouldPlaceAbove ? 'top' as const : 'bottom' as const,
    }
  }, [showMentionDropdown])

  const handleContentChange = useCallback((newValue: string) => {
    onChange(newValue)

    setTimeout(() => {
      const cursorPosition = textareaRef.current?.selectionStart || 0
      const textBeforeCursor = newValue.slice(0, cursorPosition)
      const mentionMatch = textBeforeCursor.match(/@(\w*)$/)

      if (mentionMatch) {
        setMentionQuery(mentionMatch[1]!)
        setMentionPosition(cursorPosition - mentionMatch[0].length)
        setShowMentionDropdown(true)
        setSelectedMentionIndex(0)
      }
      else {
        setShowMentionDropdown(false)
      }

      if (typeof window !== 'undefined') {
        window.requestAnimationFrame(() => {
          evaluateContentLayout()
          syncHighlightScroll()
        })
      }
    }, 0)
  }, [onChange, evaluateContentLayout, syncHighlightScroll])

  const handleMentionButtonClick = useCallback((e: React.MouseEvent) => {
    e.preventDefault()
    e.stopPropagation()

    const textarea = textareaRef.current
    if (!textarea)
      return

    const cursorPosition = textarea.selectionStart || 0
    const textBeforeCursor = value.slice(0, cursorPosition)

    if (showMentionDropdown)
      return

    if (/@\w*$/.test(textBeforeCursor))
      return

    const newContent = `${value.slice(0, cursorPosition)}@${value.slice(cursorPosition)}`

    onChange(newContent)

    setTimeout(() => {
      const newCursorPos = cursorPosition + 1
      textarea.setSelectionRange(newCursorPos, newCursorPos)
      textarea.focus()

      setMentionQuery('')
      setMentionPosition(cursorPosition)
      setShowMentionDropdown(true)
      setSelectedMentionIndex(0)

      if (typeof window !== 'undefined') {
        window.requestAnimationFrame(() => {
          evaluateContentLayout()
          syncHighlightScroll()
        })
      }
    }, 0)
  }, [value, onChange, evaluateContentLayout, syncHighlightScroll, showMentionDropdown])

  const insertMention = useCallback((user: UserProfile) => {
    const textarea = textareaRef.current
    if (!textarea)
      return

    const beforeMention = value.slice(0, mentionPosition)
    const afterMention = value.slice(textarea.selectionStart || 0)

    const needsSpaceBefore = mentionPosition > 0 && !/\s/.test(value[mentionPosition - 1]!)
    const prefix = needsSpaceBefore ? ' ' : ''
    const newContent = `${beforeMention}${prefix}@${user.name} ${afterMention}`

    onChange(newContent)
    setShowMentionDropdown(false)

    const newMentionedUserIds = [...mentionedUserIds, user.id]
    setMentionedUserIds(newMentionedUserIds)

    setTimeout(() => {
      const extraSpace = needsSpaceBefore ? 1 : 0
      const newCursorPos = mentionPosition + extraSpace + user.name.length + 2 // (space) + @ + name + space
      textarea.setSelectionRange(newCursorPos, newCursorPos)
      textarea.focus()
      if (typeof window !== 'undefined') {
        window.requestAnimationFrame(() => {
          evaluateContentLayout()
          syncHighlightScroll()
        })
      }
    }, 0)
  }, [value, mentionPosition, onChange, mentionedUserIds, evaluateContentLayout, syncHighlightScroll])

  const handleSubmit = useCallback(async (e?: React.MouseEvent) => {
    if (e) {
      e.preventDefault()
      e.stopPropagation()
    }

    if (value.trim()) {
      try {
        await onSubmit(value.trim(), mentionedUserIds)
        setMentionedUserIds([])
        setShowMentionDropdown(false)
      }
      catch (error) {
        console.error('Failed to submit', error)
      }
    }
  }, [value, mentionedUserIds, onSubmit])

  const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
    // Ignore key events during IME composition (e.g., Chinese, Japanese input)
    if (e.nativeEvent.isComposing)
      return

    if (showMentionDropdown) {
      if (e.key === 'ArrowDown') {
        e.preventDefault()
        setSelectedMentionIndex(prev =>
          prev < filteredMentionUsers.length - 1 ? prev + 1 : 0,
        )
      }
      else if (e.key === 'ArrowUp') {
        e.preventDefault()
        setSelectedMentionIndex(prev =>
          prev > 0 ? prev - 1 : filteredMentionUsers.length - 1,
        )
      }
      else if (e.key === 'Enter') {
        e.preventDefault()
        if (filteredMentionUsers[selectedMentionIndex])
          insertMention(filteredMentionUsers[selectedMentionIndex])

        return
      }
      else if (e.key === 'Escape') {
        e.preventDefault()
        setShowMentionDropdown(false)
        return
      }
    }

    if (e.key === 'Enter' && !e.shiftKey && !showMentionDropdown) {
      e.preventDefault()
      handleSubmit()
    }
  }, [showMentionDropdown, filteredMentionUsers, selectedMentionIndex, insertMention, handleSubmit])

  const resetMentionState = useCallback(() => {
    setMentionedUserIds([])
    setShowMentionDropdown(false)
    setMentionQuery('')
    setMentionPosition(0)
    setSelectedMentionIndex(0)
  }, [])

  useEffect(() => {
    if (!value) {
      Promise.resolve().then(() => {
        resetMentionState()
      })
    }
  }, [value, resetMentionState])

  useEffect(() => {
    if (!autoFocus || !textareaRef.current)
      return

    const textarea = textareaRef.current
    const timeout = window.setTimeout(() => {
      textarea.focus()
      const length = textarea.value.length
      textarea.setSelectionRange(length, length)
    }, 0)

    return () => window.clearTimeout(timeout)
  }, [autoFocus])

  return (
    <>
      <div className={cn('relative flex items-center', className)}>
        <div
          aria-hidden
          className={cn(
            'pointer-events-none absolute inset-0 z-0 overflow-hidden p-1 leading-6 wrap-break-word whitespace-pre-wrap',
            'body-lg-regular text-text-primary',
          )}
          style={{ paddingRight, paddingBottom }}
        >
          <div
            ref={highlightContentRef}
            className="min-h-full"
            style={{ willChange: 'transform' }}
          >
            {highlightedValue}

          </div>
        </div>
        <Textarea
          ref={textareaRef}
          className={cn(
            'relative z-10 w-full resize-none bg-transparent p-1 body-lg-regular leading-6 text-transparent caret-primary-500 outline-hidden',
            'placeholder:text-text-tertiary',
          )}
          style={{ paddingRight, paddingBottom }}
          placeholder={resolvedPlaceholder}
          autoFocus={autoFocus}
          minRows={isEditing ? 4 : 1}
          maxRows={4}
          value={value}
          disabled={disabled || loading}
          onChange={e => handleContentChange(e.target.value)}
          onKeyDown={handleKeyDown}
          onScroll={syncHighlightScroll}
        />

        {!isEditing && (
          <div
            ref={setActionContainerRef}
            className="absolute right-1 bottom-0 z-20 flex items-end gap-1"
          >
            <div
              className={cn(
                'z-20 flex size-8 items-center justify-center rounded-lg transition-opacity',
                shouldDisableMentionButton
                  ? 'cursor-not-allowed opacity-40'
                  : 'cursor-pointer hover:bg-state-base-hover',
              )}
              onClick={shouldDisableMentionButton ? undefined : handleMentionButtonClick}
            >
              <RiAtLine className="size-4 text-text-tertiary" />
            </div>
            <Button
              className="z-20 ml-2 w-8 px-0"
              variant="primary"
              disabled={!value.trim() || disabled || loading}
              onClick={handleSubmit}
            >
              {loading
                ? <RiLoader2Line className="size-4 animate-spin text-components-button-primary-text" />
                : <RiArrowUpLine className="size-4 text-components-button-primary-text" />}
            </Button>
          </div>
        )}

        {isEditing && (
          <div
            ref={setActionContainerRef}
            className="absolute inset-x-1 bottom-0 z-20 flex items-end justify-between"
          >
            <div
              className={cn(
                'z-20 flex size-8 items-center justify-center rounded-lg transition-opacity',
                shouldDisableMentionButton
                  ? 'cursor-not-allowed opacity-40'
                  : 'cursor-pointer hover:bg-state-base-hover',
              )}
              onClick={shouldDisableMentionButton ? undefined : handleMentionButtonClick}
            >
              <RiAtLine className="size-4 text-text-tertiary" />
            </div>
            <div
              ref={setActionRightRef}
              className="flex items-center gap-2"
            >
              <Button variant="secondary" size="small" onClick={onCancel} disabled={loading}>
                {t('operation.cancel', { ns: 'common' })}
              </Button>
              <Button
                variant="primary"
                size="small"
                disabled={loading || !value.trim()}
                onClick={() => handleSubmit()}
                className="gap-1"
              >
                {loading && <RiLoader2Line className="mr-1 size-3.5 animate-spin" />}
                <span>{t('operation.save', { ns: 'common' })}</span>
                {!loading && (
                  <EnterKey className="size-4" />
                )}
              </Button>
            </div>
          </div>
        )}
      </div>

      {showMentionDropdown && filteredMentionUsers.length > 0 && typeof document !== 'undefined' && createPortal(
        <div
          className="fixed z-9999 max-h-[248px] w-[280px] overflow-y-auto rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg/95 shadow-lg backdrop-blur-[10px]"
          style={{
            left: dropdownPosition.x,
            [dropdownPosition.placement === 'top' ? 'bottom' : 'top']: dropdownPosition.placement === 'top'
              ? window.innerHeight - dropdownPosition.y
              : dropdownPosition.y,
          }}
          data-mention-dropdown
        >
          {filteredMentionUsers.map((user, index) => (
            <div
              key={user.id}
              className={cn(
                'flex cursor-pointer items-center gap-2 rounded-md py-1 pr-3 pl-2 hover:bg-state-base-hover',
                index === selectedMentionIndex && 'bg-state-base-hover',
              )}
              onClick={() => insertMention(user)}
            >
              <Avatar
                avatar={user.avatar_url || null}
                name={user.name}
                size="sm"
                className="shrink-0"
              />
              <div className="min-w-0 flex-1">
                <div className="truncate text-sm font-medium text-text-primary">
                  {user.name}
                </div>
                <div className="truncate text-xs text-text-tertiary">
                  {user.email}
                </div>
              </div>
            </div>
          ))}
        </div>,
        document.body,
      )}
    </>
  )
})

MentionInputInner.displayName = 'MentionInputInner'

export const MentionInput = memo(MentionInputInner)
