import React, { useState, useCallback, useRef, useEffect, memo } from 'react'
import type { EditorView } from '@milkdown/prose/view'
import { TextSelection } from '@milkdown/prose/state'
import type { Node as ProseMirrorNode } from '@milkdown/prose/model'
import { searchPluginKey, type SearchMatch, type SearchPluginState } from '../Editor/useMilkdown'

interface SearchReplaceProps {
  /** 获取 ProseMirror EditorView 实例 */
  getView: () => EditorView | null
  /** 关闭面板 */
  onClose: () => void
}

/**
 * 在 ProseMirror 文档中查找所有匹配位置
 * D6: 支持正则表达式 + 大小写敏感
 */
function findMatches(doc: ProseMirrorNode, query: string, caseSensitive: boolean, useRegex: boolean): SearchMatch[] {
  const matches: SearchMatch[] = []
  if (!query) return matches

  if (useRegex) {
    let regex: RegExp
    try {
      const flags = caseSensitive ? 'g' : 'gi'
      regex = new RegExp(query, flags)
    } catch {
      // 正则语法错误 — 返回空，UI 通过 regexError 状态提示用户
      return []
    }
    doc.descendants((node, pos) => {
      if (!node.isText) return true
      const text = node.text || ''
      regex.lastIndex = 0
      let result: RegExpExecArray | null
      while ((result = regex.exec(text)) !== null) {
        matches.push({ from: pos + result.index, to: pos + result.index + result[0].length })
        if (result[0].length === 0) regex.lastIndex++ // 避免空匹配死循环
      }
      return true
    })
    return matches
  }

  // 普通字符串匹配
  const normalizedQuery = caseSensitive ? query : query.toLowerCase()
  doc.descendants((node, pos) => {
    if (!node.isText) return true
    const text = node.text || ''
    const searchText = caseSensitive ? text : text.toLowerCase()
    let offset = 0
    let idx: number
    while ((idx = searchText.indexOf(normalizedQuery, offset)) !== -1) {
      matches.push({ from: pos + idx, to: pos + idx + query.length })
      offset = idx + 1
    }
    return true
  })

  return matches
}

/**
 * 滚动到指定文档位置
 */
function scrollToPos(view: EditorView, pos: number) {
  try {
    const { node } = view.domAtPos(pos)
    const el = node.nodeType === Node.TEXT_NODE ? node.parentElement : (node as HTMLElement)
    el?.scrollIntoView({ behavior: 'smooth', block: 'center' })
  } catch {
    // Position may be out of range
  }
}

/**
 * 更新 ProseMirror plugin state（触发 decorations 重绘）
 */
function dispatchSearchState(view: EditorView, state: SearchPluginState) {
  const tr = view.state.tr.setMeta(searchPluginKey, state)
  view.dispatch(tr)
}

/**
 * 清除搜索高亮（dispatch 空状态）
 */
function clearSearchDecorations(view: EditorView) {
  dispatchSearchState(view, { matches: [], currentIndex: -1, query: '' })
}

/**
 * SearchReplace — 编辑器内搜索/替换面板
 *
 * 功能:
 * - Ctrl+F 打开搜索，Ctrl+H 打开替换
 * - 大小写敏感切换
 * - Enter 下一个，Shift+Enter 上一个
 * - 替换当前匹配 / 全部替换
 * - ESC 关闭面板
 */
export const SearchReplace = memo(function SearchReplace({
  getView,
  onClose
}: SearchReplaceProps) {
  const [query, setQuery] = useState('')
  const [replacement, setReplacement] = useState('')
  const [showReplace, setShowReplace] = useState(false)
  const [caseSensitive, setCaseSensitive] = useState(false)
  const [useRegex, setUseRegex] = useState(false)
  const [regexError, setRegexError] = useState(false)
  const [matchCount, setMatchCount] = useState(0)
  const [currentMatch, setCurrentMatch] = useState(-1)

  const searchInputRef = useRef<HTMLInputElement>(null)
  const panelRef = useRef<HTMLDivElement>(null)

  // Keep latest state in refs for use in callbacks without stale closures
  const matchesRef = useRef<SearchMatch[]>([])
  const currentIndexRef = useRef(-1)
  const caseSensitiveRef = useRef(false)
  const useRegexRef = useRef(false)
  const queryRef = useRef('')

  // Sync refs
  caseSensitiveRef.current = caseSensitive
  useRegexRef.current = useRegex
  queryRef.current = query

  // 执行搜索
  const doSearch = useCallback((searchQuery: string, cs: boolean, rx: boolean) => {
    const view = getView()
    if (!view) return

    // D6: 检查正则语法
    setRegexError(false)
    if (rx && searchQuery.trim()) {
      try {
        new RegExp(searchQuery, 'g')
      } catch {
        setRegexError(true)
        return
      }
    }

    if (!searchQuery.trim()) {
      clearSearchDecorations(view)
      matchesRef.current = []
      currentIndexRef.current = -1
      setMatchCount(0)
      setCurrentMatch(-1)
      return
    }

    const matches = findMatches(view.state.doc, searchQuery, cs, rx)
    matchesRef.current = matches
    const newIdx = matches.length > 0 ? 0 : -1
    currentIndexRef.current = newIdx

    setMatchCount(matches.length)
    setCurrentMatch(newIdx)

    // Dispatch to ProseMirror plugin to render decorations
    dispatchSearchState(view, { matches, currentIndex: newIdx, query: searchQuery })

    // Scroll to first match
    if (matches.length > 0) {
      scrollToPos(view, matches[0].from)
    }
  }, [getView])

  // 搜索框输入变化
  const handleQueryChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value
    setQuery(value)
    doSearch(value, caseSensitiveRef.current, useRegexRef.current)
  }, [doSearch])

  // 大小写切换
  const toggleCaseSensitive = useCallback(() => {
    const newCS = !caseSensitiveRef.current
    setCaseSensitive(newCS)
    doSearch(queryRef.current, newCS, useRegexRef.current)
  }, [doSearch])

  // 正则切换
  const toggleRegex = useCallback(() => {
    const newRx = !useRegexRef.current
    setUseRegex(newRx)
    doSearch(queryRef.current, caseSensitiveRef.current, newRx)
  }, [doSearch])

  // 导航到下一个/上一个匹配
  const navigateMatch = useCallback((direction: 1 | -1) => {
    const view = getView()
    if (!view) return

    const matches = matchesRef.current
    if (matches.length === 0) return

    let next = currentIndexRef.current + direction
    if (next >= matches.length) next = 0
    if (next < 0) next = matches.length - 1

    currentIndexRef.current = next
    setCurrentMatch(next)

    // Update plugin state to move the active highlight
    dispatchSearchState(view, {
      matches,
      currentIndex: next,
      query: queryRef.current
    })

    // Scroll to match and select it
    const match = matches[next]
    try {
      const tr = view.state.tr.setSelection(
        TextSelection.create(view.state.doc, match.from, match.to)
      )
      view.dispatch(tr)
    } catch {
      // Selection range might be invalid
    }
    scrollToPos(view, match.from)
  }, [getView])

  // 替换当前匹配
  const replaceCurrent = useCallback(() => {
    const view = getView()
    if (!view) return

    const matches = matchesRef.current
    const idx = currentIndexRef.current
    if (idx < 0 || idx >= matches.length) return

    const match = matches[idx]

    // Replace text in ProseMirror document
    const tr = view.state.tr.insertText(replacement, match.from, match.to)
    view.dispatch(tr)

    // Re-search after replacement (document changed)
    // Use requestAnimationFrame to let ProseMirror process the transaction
    requestAnimationFrame(() => {
      doSearch(queryRef.current, caseSensitiveRef.current, useRegexRef.current)
    })
  }, [getView, replacement, doSearch])

  // 全部替换
  const replaceAll = useCallback(() => {
    const view = getView()
    if (!view) return

    // 重新搜索以获取匹配的最新位置
    const freshMatches = findMatches(view.state.doc, queryRef.current, caseSensitiveRef.current, useRegexRef.current)
    if (freshMatches.length === 0) return
    if (freshMatches.length === 0) return

    // 从后往前替换以保持位置正确
    const tr = view.state.tr
    for (let i = freshMatches.length - 1; i >= 0; i--) {
      tr.insertText(replacement, freshMatches[i].from, freshMatches[i].to)
    }
    view.dispatch(tr)

    // 更新ref
    matchesRef.current = []

    // 替换后重新搜索
    requestAnimationFrame(() => {
      doSearch(queryRef.current, caseSensitiveRef.current, useRegexRef.current)
    })
  }, [getView, replacement, doSearch])

  // 键盘事件
  const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
    if (e.key === 'Escape') {
      e.preventDefault()
      const view = getView()
      if (view) clearSearchDecorations(view)
      onClose()
      return
    }
    if (e.key === 'Enter') {
      e.preventDefault()
      if (e.shiftKey) {
        navigateMatch(-1)
      } else {
        navigateMatch(1)
      }
    }
  }, [onClose, navigateMatch, getView])

  // 全局 Ctrl+F / Ctrl+H 拦截
  useEffect(() => {
    const handleGlobalKey = (e: KeyboardEvent) => {
      const isCtrl = e.ctrlKey || e.metaKey
      if (isCtrl && e.key === 'f') {
        e.preventDefault()
        setShowReplace(false)
        searchInputRef.current?.focus()
        searchInputRef.current?.select()
      }
      if (isCtrl && e.key === 'h') {
        e.preventDefault()
        setShowReplace(true)
        searchInputRef.current?.focus()
        searchInputRef.current?.select()
      }
    }
    document.addEventListener('keydown', handleGlobalKey)
    return () => document.removeEventListener('keydown', handleGlobalKey)
  }, [])

  // 打开时聚焦搜索框
  useEffect(() => {
    searchInputRef.current?.focus()
    searchInputRef.current?.select()
  }, [])

  // 清理高亮（组件卸载时）
  useEffect(() => {
    return () => {
      const view = getView()
      if (view) clearSearchDecorations(view)
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  return (
    <div
      ref={panelRef}
      className="search-replace-panel"
      role="search"
      aria-label="搜索和替换"
      onKeyDown={handleKeyDown}
    >
      <div className="search-row">
        <div className="search-input-group">
          <input
            ref={searchInputRef}
            type="text"
            className="search-input"
            placeholder="搜索..."
            value={query}
            onChange={handleQueryChange}
            aria-label="搜索文本"
          />
          <span className="search-count" aria-live="polite">
            {regexError ? '正则语法错误' : (matchCount > 0 ? `${currentMatch + 1}/${matchCount}` : query ? '无匹配' : '')}
          </span>
        </div>
        <button
          className={`search-btn ${caseSensitive ? 'active' : ''}`}
          onClick={toggleCaseSensitive}
          title="区分大小写 (点击切换)"
          aria-label="区分大小写"
          aria-pressed={caseSensitive}
        >
          Aa
        </button>
        <button
          className={`search-btn ${useRegex ? 'active' : ''}`}
          onClick={toggleRegex}
          title="使用正则表达式 (点击切换)"
          aria-label="正则表达式"
          aria-pressed={useRegex}
        >
          .*
        </button>
        <button
          className="search-btn"
          onClick={() => navigateMatch(-1)}
          title="上一个 (Shift+Enter)"
          aria-label="上一个匹配"
          disabled={matchCount === 0}
        >
          ↑
        </button>
        <button
          className="search-btn"
          onClick={() => navigateMatch(1)}
          title="下一个 (Enter)"
          aria-label="下一个匹配"
          disabled={matchCount === 0}
        >
          ↓
        </button>
        <button
          className="search-btn"
          onClick={() => setShowReplace(!showReplace)}
          title="切换替换模式 (Ctrl+H)"
          aria-label="替换模式"
          aria-expanded={showReplace}
        >
          ⇄
        </button>
        <button
          className="search-btn search-close"
          onClick={() => {
            const view = getView()
            if (view) clearSearchDecorations(view)
            onClose()
          }}
          title="关闭 (Esc)"
          aria-label="关闭搜索"
        >
          ✕
        </button>
      </div>
      {showReplace && (
        <div className="search-row">
          <div className="search-input-group">
            <input
              type="text"
              className="search-input"
              placeholder="替换..."
              value={replacement}
              onChange={e => setReplacement(e.target.value)}
              aria-label="替换文本"
            />
          </div>
          <button
            className="search-btn replace-btn"
            onClick={replaceCurrent}
            title="替换当前"
            aria-label="替换当前匹配"
            disabled={currentMatch < 0}
          >
            替换
          </button>
          <button
            className="search-btn replace-btn"
            onClick={replaceAll}
            title="全部替换"
            aria-label="替换全部匹配"
            disabled={matchCount === 0}
          >
            全部
          </button>
        </div>
      )}
    </div>
  )
})

SearchReplace.displayName = 'SearchReplace'
