import { h } from 'preact';
import { useState, useMemo } from 'preact/hooks';

// A single fuzzy-finder input for picking a signal as one "ds:name" reference,
// replacing the old stacked "Data source" + "Signal" select pair. The caller
// supplies a flattened option list (every ds:name it knows about) and, via
// onOpen, a hook to lazily load signals for all data sources when the dropdown
// is first opened. When allowFreeform is set, typing a value that matches no
// option (e.g. a new srv:/local: variable) can still be committed.

export interface SignalOption { ds: string; name: string; }

export default function SignalPicker({
  value,
  options,
  onChange,
  onOpen,
  placeholder = 'Search signals…',
  allowFreeform = false,
}: {
  value: string; // current "ds:name" (or "")
  options: SignalOption[];
  onChange: (ref: string) => void;
  onOpen?: () => void;
  placeholder?: string;
  allowFreeform?: boolean;
}) {
  const [open, setOpen] = useState(false);
  const [filter, setFilter] = useState('');

  const filtered = useMemo(() => {
    const tokens = filter.toLowerCase().split(/\s+/).filter(Boolean);
    const scored = options
      .map(o => ({ o, label: `${o.ds}:${o.name}` }))
      .filter(({ label }) => {
        const l = label.toLowerCase();
        return tokens.every(t => l.includes(t));
      });
    // Prefer matches where the signal name (not the ds prefix) leads.
    const f = filter.toLowerCase();
    scored.sort((a, b) => {
      const an = a.o.name.toLowerCase().startsWith(f) ? 0 : 1;
      const bn = b.o.name.toLowerCase().startsWith(f) ? 0 : 1;
      if (an !== bn) return an - bn;
      return a.label.localeCompare(b.label);
    });
    return scored.slice(0, 200);
  }, [options, filter]);

  function openDropdown() {
    setOpen(true);
    setFilter('');
    onOpen?.();
  }

  function commit(ref: string) {
    onChange(ref);
    setOpen(false);
    setFilter('');
  }

  // A freeform entry is offered when the typed text looks like a usable
  // reference and is not already an exact option.
  const freeform = allowFreeform && filter.trim() !== ''
    && !filtered.some(({ label }) => label === filter.trim());

  return (
    <div class="search-select">
      <div class="search-select-trigger" onClick={() => (open ? setOpen(false) : openDropdown())}>
        {value || <span class="search-select-placeholder">{placeholder}</span>}
        <span class="search-select-arrow">{open ? '▴' : '▾'}</span>
      </div>
      {open && (
        <div class="search-select-dropdown">
          <input
            class="prop-input"
            autoFocus
            placeholder="Type to search… (ds:name)"
            value={filter}
            onInput={(e) => setFilter((e.target as HTMLInputElement).value)}
            onClick={(e) => e.stopPropagation()}
            onKeyDown={(e: KeyboardEvent) => {
              if (e.key === 'Enter') {
                e.preventDefault();
                if (filtered.length > 0) commit(`${filtered[0].o.ds}:${filtered[0].o.name}`);
                else if (freeform) commit(filter.trim());
              }
              if (e.key === 'Escape') { e.preventDefault(); setOpen(false); }
            }}
          />
          <div class="search-select-list">
            {freeform && (
              <div class="search-select-item" onClick={() => commit(filter.trim())}>
                Use “{filter.trim()}”
              </div>
            )}
            {filtered.length === 0 && !freeform && <div class="search-select-item hint">No matches</div>}
            {filtered.map(({ o, label }) => (
              <div
                key={label}
                class={`search-select-item${label === value ? ' search-select-item-selected' : ''}`}
                onClick={() => commit(`${o.ds}:${o.name}`)}
              >
                {label}
              </div>
            ))}
          </div>
        </div>
      )}
      {open && <div class="search-select-backdrop" onClick={() => setOpen(false)} />}
    </div>
  );
}
