import { useCallback, useEffect, useRef, useState } from 'react'
import type { Editor } from '@tiptap/react'
import { clearSearch, getSearchState, setActive, setSearch } from './SearchHighlight'

// FindReplace is the in-document search bar (Ctrl/Cmd+F). It drives the
// SearchHighlight decoration layer: typing updates the highlighted matches, the
// arrows step through them (scrolling each into view), and replace / replace-all
// edit the document in place. Bilingual, zh-first, matching the editor chrome.

interface Props {
  editor: Editor
  onClose: () => void
}

const CJK = "'Nunito','PingFang SC','Microsoft YaHei','Noto Sans CJK SC',sans-serif"

// Scroll the active match into the middle of the viewport without moving the
// selection (which would otherwise pop the rewrite bubble).
function scrollToActive(editor: Editor) {
  const st = getSearchState(editor.state)
  if (st.active < 0) return
  const m = st.matches[st.active]
  const dom = editor.view.domAtPos(m.from)
  const el = dom.node.nodeType === Node.TEXT_NODE ? dom.node.parentElement : (dom.node as HTMLElement)
  el?.scrollIntoView({ block: 'center', behavior: 'smooth' })
}

export function FindReplace({ editor, onClose }: Props) {
  const [query, setQuery] = useState('')
  const [replacement, setReplacement] = useState('')
  const [caseSensitive, setCaseSensitive] = useState(false)
  const [showReplace, setShowReplace] = useState(false)
  // Mirror of the plugin's match count + active index for the "n / m" readout.
  const [{ count, active }, setCounts] = useState({ count: 0, active: -1 })
  const findRef = useRef<HTMLInputElement>(null)

  const sync = useCallback(() => {
    const st = getSearchState(editor.state)
    setCounts({ count: st.matches.length, active: st.active })
  }, [editor])

  // Push the query into the decoration layer whenever it (or case-sensitivity)
  // changes, then jump to the first match.
  useEffect(() => {
    setSearch(editor.state, editor.view.dispatch, query, caseSensitive)
    sync()
    scrollToActive(editor)
  }, [editor, query, caseSensitive, sync])

  // Tear the highlight layer down when the bar closes.
  useEffect(() => () => clearSearch(editor.state, editor.view.dispatch), [editor])

  // Focus the find field on open.
  useEffect(() => {
    findRef.current?.focus()
    findRef.current?.select()
  }, [])

  const go = useCallback(
    (delta: number) => {
      const st = getSearchState(editor.state)
      if (!st.matches.length) return
      setActive(editor.state, editor.view.dispatch, st.active + delta)
      sync()
      scrollToActive(editor)
    },
    [editor, sync],
  )

  const replaceActive = useCallback(() => {
    const st = getSearchState(editor.state)
    if (st.active < 0) return
    const m = st.matches[st.active]
    const tr = editor.state.tr
    if (replacement) tr.insertText(replacement, m.from, m.to)
    else tr.delete(m.from, m.to)
    editor.view.dispatch(tr)
    sync()
    scrollToActive(editor)
  }, [editor, replacement, sync])

  const replaceAll = useCallback(() => {
    const st = getSearchState(editor.state)
    if (!st.matches.length) return
    const tr = editor.state.tr
    // Apply back-to-front so earlier edits don't shift later match positions.
    for (let i = st.matches.length - 1; i >= 0; i--) {
      const m = st.matches[i]
      if (replacement) tr.insertText(replacement, m.from, m.to)
      else tr.delete(m.from, m.to)
    }
    editor.view.dispatch(tr)
    sync()
  }, [editor, replacement, sync])

  const inputStyle: React.CSSProperties = {
    borderRadius: 'var(--radius-input)',
    border: '1px solid var(--color-border)',
    color: 'var(--color-plum)',
    fontFamily: CJK,
  }

  return (
    <div
      className="petal-no-print absolute right-2 top-2 z-40 flex flex-col gap-1.5 p-2"
      role="dialog"
      aria-label="Find and replace"
      onKeyDown={(e) => {
        if (e.key === 'Escape') {
          e.preventDefault()
          onClose()
        }
      }}
      style={{
        width: 320,
        background: 'var(--color-surface)',
        border: '1px solid var(--color-border)',
        borderRadius: 'var(--radius-card)',
        boxShadow: 'var(--shadow-soft)',
      }}
    >
      <div className="flex items-center gap-1.5">
        <button
          type="button"
          onClick={() => setShowReplace((v) => !v)}
          aria-label={showReplace ? 'Hide replace' : 'Show replace'}
          title={showReplace ? 'Hide replace' : 'Show replace'}
          className="flex h-8 w-6 items-center justify-center text-xs"
          style={{ color: 'var(--color-muted)' }}
        >
          {showReplace ? '▾' : '▸'}
        </button>
        <input
          ref={findRef}
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === 'Enter') {
              e.preventDefault()
              go(e.shiftKey ? -1 : 1)
            }
          }}
          placeholder="查找 · Find"
          className="min-w-0 flex-1 px-2 py-1.5 text-sm focus:outline-none"
          style={inputStyle}
        />
        <span
          className="w-14 shrink-0 text-center text-xs tabular-nums"
          style={{ color: 'var(--color-muted)' }}
        >
          {count ? `${active + 1} / ${count}` : query ? '无 · 0' : ''}
        </span>
        <FindBtn label="Previous match" disabled={!count} onClick={() => go(-1)}>↑</FindBtn>
        <FindBtn label="Next match" disabled={!count} onClick={() => go(1)}>↓</FindBtn>
        <FindBtn
          label="Match case"
          active={caseSensitive}
          onClick={() => setCaseSensitive((v) => !v)}
          title="Match case · 区分大小写"
        >
          Aa
        </FindBtn>
        <FindBtn label="Close" onClick={onClose} title="Close · 关闭">✕</FindBtn>
      </div>

      {showReplace && (
        <div className="flex items-center gap-1.5 pl-7">
          <input
            value={replacement}
            onChange={(e) => setReplacement(e.target.value)}
            onKeyDown={(e) => {
              if (e.key === 'Enter') {
                e.preventDefault()
                replaceActive()
              }
            }}
            placeholder="替换为 · Replace"
            className="min-w-0 flex-1 px-2 py-1.5 text-sm focus:outline-none"
            style={inputStyle}
          />
          <button
            type="button"
            onClick={replaceActive}
            disabled={!count}
            className="h-8 shrink-0 whitespace-nowrap px-2.5 text-xs font-semibold disabled:opacity-40"
            style={{ borderRadius: 'var(--radius-pill)', background: 'var(--color-surface-alt)', color: 'var(--color-plum)' }}
          >
            替换
          </button>
          <button
            type="button"
            onClick={replaceAll}
            disabled={!count}
            className="h-8 shrink-0 whitespace-nowrap px-2.5 text-xs font-bold disabled:opacity-40"
            style={{ borderRadius: 'var(--radius-pill)', background: 'var(--color-accent)', color: '#fff' }}
          >
            全部
          </button>
        </div>
      )}
    </div>
  )
}

function FindBtn({
  children,
  onClick,
  label,
  title,
  active,
  disabled,
}: {
  children: React.ReactNode
  onClick: () => void
  label: string
  title?: string
  active?: boolean
  disabled?: boolean
}) {
  return (
    <button
      type="button"
      aria-label={label}
      title={title ?? label}
      aria-pressed={active}
      disabled={disabled}
      onClick={onClick}
      className="flex h-8 min-w-8 items-center justify-center px-1.5 text-sm font-semibold disabled:opacity-40"
      style={{
        borderRadius: 'var(--radius-input)',
        color: active ? 'var(--color-accent-hover)' : 'var(--color-muted)',
        background: active ? 'var(--color-surface-alt)' : 'transparent',
      }}
    >
      {children}
    </button>
  )
}
