'use client'

import { useEffect, useMemo, useRef, useState } from 'react'

export type ComboboxOption = {
  /** Value submitted with the form. */
  id: string
  /** What the user sees + types against. */
  label: string
}

type Props = {
  /** name="" on the hidden input that the form submits. */
  name: string
  options: ComboboxOption[]
  /** Placeholder text in the search input. */
  placeholder?: string
  /** Optional value to pre-select. */
  defaultId?: string | null
  /** Width / styling on the wrapping container. */
  style?: React.CSSProperties
  /** Optional dark-mode styling for the input (used inside the bulk-action
   * toolbar which has a black background). */
  dark?: boolean
}

/**
 * Minimal type-to-filter combobox. Picks an existing option only —
 * doesn't accept arbitrary free text. The form submits the picked
 * option's `id` (or empty string when nothing's picked).
 *
 * Behavior:
 *   - Typing filters the visible options (case-insensitive contains).
 *   - Up / Down arrow keys move the highlight; Enter picks.
 *   - Esc closes the dropdown without picking.
 *   - Clicking outside closes the dropdown.
 *   - Once picked, the text input shows the picked label and the
 *     hidden input carries the id.
 *   - Clearing the input via backspace re-opens the dropdown.
 */
export function SearchableCombobox({
  name,
  options,
  placeholder,
  defaultId = null,
  style,
  dark = false,
}: Props) {
  const initial =
    defaultId != null
      ? (options.find((o) => o.id === defaultId) ?? null)
      : null
  const [selected, setSelected] = useState<ComboboxOption | null>(initial)
  const [query, setQuery] = useState('')
  const [open, setOpen] = useState(false)
  const [hi, setHi] = useState(0)
  const wrapRef = useRef<HTMLDivElement>(null)
  const listRef = useRef<HTMLUListElement>(null)

  const filtered = useMemo(() => {
    const q = query.trim().toLowerCase()
    if (!q) return options
    return options.filter((o) => o.label.toLowerCase().includes(q))
  }, [options, query])

  // Close when the user clicks outside the wrapper.
  useEffect(() => {
    if (!open) return
    function onDown(e: MouseEvent) {
      if (!wrapRef.current) return
      if (!wrapRef.current.contains(e.target as Node)) setOpen(false)
    }
    document.addEventListener('mousedown', onDown)
    return () => document.removeEventListener('mousedown', onDown)
  }, [open])

  // Keep the highlighted option scrolled into view.
  useEffect(() => {
    if (!open) return
    const ul = listRef.current
    if (!ul) return
    const el = ul.children[hi] as HTMLElement | undefined
    el?.scrollIntoView({ block: 'nearest' })
  }, [hi, open])

  function pick(opt: ComboboxOption) {
    setSelected(opt)
    setQuery('')
    setOpen(false)
  }

  function onKeyDown(e: React.KeyboardEvent<HTMLInputElement>) {
    if (e.key === 'ArrowDown') {
      e.preventDefault()
      setOpen(true)
      setHi((h) => Math.min(filtered.length - 1, h + 1))
    } else if (e.key === 'ArrowUp') {
      e.preventDefault()
      setHi((h) => Math.max(0, h - 1))
    } else if (e.key === 'Enter') {
      if (open && filtered[hi]) {
        e.preventDefault()
        pick(filtered[hi])
      }
    } else if (e.key === 'Escape') {
      setOpen(false)
    }
  }

  const inputBg = dark ? '#1a1a1a' : '#fff'
  const inputColor = dark ? '#fff' : '#000'
  const dropBg = dark ? '#1a1a1a' : '#fff'
  const dropBorder = dark ? '1px solid #333' : '1px solid #ccc'

  return (
    <div ref={wrapRef} style={{ position: 'relative', ...style }}>
      <input type="hidden" name={name} value={selected?.id ?? ''} />
      <input
        type="text"
        value={selected ? selected.label : query}
        onChange={(e) => {
          setQuery(e.target.value)
          setSelected(null)
          setOpen(true)
          setHi(0)
        }}
        onFocus={() => setOpen(true)}
        onKeyDown={onKeyDown}
        placeholder={placeholder}
        autoComplete="off"
        style={{
          width: '100%',
          padding: '0.35rem 0.5rem',
          fontSize: '0.85rem',
          background: inputBg,
          color: inputColor,
          border: dropBorder,
          borderRadius: 4,
        }}
      />
      {open && filtered.length > 0 && (
        <ul
          ref={listRef}
          style={{
            position: 'absolute',
            top: '100%',
            left: 0,
            right: 0,
            zIndex: 10,
            maxHeight: '14rem',
            overflowY: 'auto',
            margin: 0,
            padding: 0,
            listStyle: 'none',
            background: dropBg,
            color: inputColor,
            border: dropBorder,
            borderRadius: 4,
            boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
            fontSize: '0.85rem',
          }}
        >
          {filtered.map((opt, i) => (
            <li
              key={opt.id}
              onMouseDown={(e) => {
                // mousedown not click — fires before blur, so we can
                // still mutate state before the input loses focus.
                e.preventDefault()
                pick(opt)
              }}
              onMouseEnter={() => setHi(i)}
              style={{
                padding: '0.3rem 0.6rem',
                background: i === hi ? (dark ? '#2a2a2a' : '#eef') : 'transparent',
                cursor: 'pointer',
              }}
            >
              {opt.label}
            </li>
          ))}
        </ul>
      )}
      {open && filtered.length === 0 && (
        <div
          style={{
            position: 'absolute',
            top: '100%',
            left: 0,
            right: 0,
            zIndex: 10,
            padding: '0.4rem 0.6rem',
            background: dropBg,
            color: dark ? '#888' : '#666',
            border: dropBorder,
            borderRadius: 4,
            fontSize: '0.8rem',
          }}
        >
          No matches.
        </div>
      )}
    </div>
  )
}
