/* global React */
const { useState, useEffect, useRef, useMemo } = React;

/**
 * Text filter and dropdown in one control.
 *
 * The list narrows to options whose label contains every whitespace-separated
 * token typed, in any order and case-insensitively, so "haren pub" finds
 * "Van Haren Publishing B.V." without knowing the word order.
 *
 * Use this for lists that grow: relations, contacts, invoices, payment methods.
 * A fixed three-option enum such as a status filter stays a native <select>,
 * where typing to filter is slower than just picking.
 *
 * Props:
 *   value       currently selected option value, or '' for none
 *   onChange    (value) => void, receives '' when cleared
 *   options     [{ value, label }]
 *   placeholder shown when nothing is selected
 *   emptyLabel  label of the "no selection" row; omit allowEmpty to hide it
 */
function Combobox({
  value,
  onChange,
  options,
  placeholder = 'Type to search',
  emptyLabel = '— none —',
  allowEmpty = true,
  disabled = false,
  compact = false,
}) {
  const [open, setOpen]     = useState(false);
  const [query, setQuery]   = useState('');
  const [active, setActive] = useState(0);
  const wrapRef  = useRef(null);
  const inputRef = useRef(null);
  const listRef  = useRef(null);

  const selected = options.find(o => String(o.value) === String(value)) || null;

  // The empty row is part of the same list so keyboard navigation and the
  // scroll-into-view lookup share one index space with the filtered options.
  const items = useMemo(() => {
    const tokens = query.trim().toLowerCase().split(/\s+/).filter(Boolean);
    const matched = tokens.length
      ? options.filter(o => {
          const label = String(o.label).toLowerCase();
          return tokens.every(t => label.includes(t));
        })
      : options;
    return (allowEmpty && !tokens.length ? [{ value: '', label: emptyLabel, isEmpty: true }] : []).concat(matched);
  }, [options, query, allowEmpty, emptyLabel]);

  // Outside click closes and drops the query, so the input shows the selection again
  useEffect(() => {
    if (!open) return;
    const onDown = e => {
      if (wrapRef.current && !wrapRef.current.contains(e.target)) {
        setOpen(false);
        setQuery('');
      }
    };
    document.addEventListener('mousedown', onDown);
    return () => document.removeEventListener('mousedown', onDown);
  }, [open]);

  useEffect(() => { setActive(0); }, [query, open]);

  useEffect(() => {
    if (!open || !listRef.current) return;
    const el = listRef.current.children[active];
    if (el && el.scrollIntoView) el.scrollIntoView({ block: 'nearest' });
  }, [active, open]);

  function close() { setOpen(false); setQuery(''); }
  function pick(opt) { onChange(opt && !opt.isEmpty ? opt.value : ''); close(); }

  function onKeyDown(e) {
    if (e.key === 'ArrowDown' || e.key === 'ArrowUp') {
      e.preventDefault();
      if (!open) { setOpen(true); return; }
      if (!items.length) return;
      setActive(a => (e.key === 'ArrowDown' ? (a + 1) % items.length : (a - 1 + items.length) % items.length));
    } else if (e.key === 'Enter') {
      if (!open) return;
      e.preventDefault();
      if (items[active]) pick(items[active]);
    } else if (e.key === 'Escape') {
      if (open) { e.preventDefault(); close(); }
    } else if (e.key === 'Tab') {
      close();
    }
  }

  return (
    <div className={'combobox' + (disabled ? ' combobox--disabled' : '') + (compact ? ' combobox--compact' : '')} ref={wrapRef}>
      <input
        ref={inputRef}
        type="text"
        className="field-input combobox__input"
        role="combobox"
        aria-expanded={open}
        aria-autocomplete="list"
        autoComplete="off"
        disabled={disabled}
        value={open ? query : (selected ? selected.label : '')}
        placeholder={selected ? selected.label : placeholder}
        onChange={e => { setQuery(e.target.value); setOpen(true); }}
        onFocus={() => setOpen(true)}
        onKeyDown={onKeyDown}
      />
      {allowEmpty && value && !open ? (
        <button
          type="button"
          className="combobox__clear"
          tabIndex={-1}
          aria-label="Clear selection"
          onClick={() => { onChange(''); close(); }}>
          ×
        </button>
      ) : null}
      {open && (
        <ul className="combobox__list" ref={listRef} role="listbox">
          {items.map((o, i) => (
            <li
              key={o.isEmpty ? '__empty__' : o.value}
              role="option"
              aria-selected={String(o.value) === String(value)}
              className={
                'combobox__option'
                + (o.isEmpty ? ' combobox__option--empty' : '')
                + (i === active ? ' is-active' : '')
                + (!o.isEmpty && String(o.value) === String(value) ? ' is-selected' : '')
              }
              onMouseEnter={() => setActive(i)}
              // mousedown, not click: the input's blur would close the list first
              onMouseDown={e => { e.preventDefault(); pick(o); }}>
              {o.label}
            </li>
          ))}
          {!items.length && <li className="combobox__none">No match</li>}
        </ul>
      )}
    </div>
  );
}

window.Combobox = Combobox;
