/* ========================================================================
   EDITOR POPOVER — React port of the filter value editor (04-search-render).
   One popover, anchored to the active field-list row / chip, with a value
   editor per field type: text, select, multi (tri-state), number, themes /
   persona / target (tag combobox), entity, tokens, source.
   ======================================================================== */

/* ---- facet counts ------------------------------------------------------- */
function facetContextFor(field) {
  const others = fpState().filters.filter(f => f.fieldId !== field.id && isFilterComplete(f));
  const key = fpState().entity + '|' + field.id + '|' + JSON.stringify(others.map(f => [f.fieldId, f.op, f.value]));
  if (fpState()._facetCtxCache && fpState()._facetCtxCache.key === key) return fpState()._facetCtxCache.records;
  const records = applyFilters(datasetFor(fpState().entity), others, fpState().entity);
  fpState()._facetCtxCache = { key, records };
  return records;
}
function facetTrialFilter(field, option) {
  switch (field.type) {
    case 'multi':   return { id:'_t', fieldId: field.id, op:'anyOf',     value: { include:[option], exclude:[] } };
    case 'select':  return { id:'_t', fieldId: field.id, op:'is',        value: option };
    case 'themes':  return { id:'_t', fieldId: field.id, op:'themesAny', value: [option] };
    case 'persona': return { id:'_t', fieldId: field.id, op:'persIs',    value: [option] };
    case 'target':  return { id:'_t', fieldId: field.id, op:'tgtIs',     value: [option] };
    case 'entity':  return { id:'_t', fieldId: field.id, op:'entIs',     value: option };
    case 'text':    return { id:'_t', fieldId: field.id, op:'equals',    value: option };
    default:        return null;
  }
}
function facetCountForOption(records, field, option) {
  const trial = facetTrialFilter(field, option);
  if (!trial) return null;
  try { return applyFilters(records, [trial], fpState().entity).length; }
  catch (_) { return null; }
}
function fmtFacet(n) {
  if (n == null) return '';
  if (n >= 1000) return n.toLocaleString();
  return String(n);
}
function PillCount({ n }) {
  if (n == null) return null;
  return <span className={cx('ve-pill-count', n === 0 && 'zero')}>{fmtFacet(n)}</span>;
}
function ComboCount({ n }) {
  if (n == null) return null;
  return <span className={cx('vc-count', n === 0 && 'zero')}>{fmtFacet(n)}</span>;
}

const _uniqueValueCache = {};
function uniqueValuesFor(field) {
  if (_uniqueValueCache[field.id]) return _uniqueValueCache[field.id];
  const seen = new Set();
  datasetFor(field.entity).forEach(r => {
    const v = r[field.key];
    if (v != null && v !== '') seen.add(String(v));
  });
  const arr = [...seen].sort();
  _uniqueValueCache[field.id] = arr;
  return arr;
}

function HighlightMatch({ text, query }) {
  if (!query) return <span>{text}</span>;
  const lower = String(text).toLowerCase();
  const q = String(query).toLowerCase();
  const i = lower.indexOf(q);
  if (i === -1) return <span>{text}</span>;
  return (
    <span>{text.slice(0, i)}<span className="vc-match">{text.slice(i, i + q.length)}</span>{text.slice(i + q.length)}</span>
  );
}

function presetCatalog(field) {
  if (field.catalog === 'titles') return { items: TITLES_CATALOG, allowCustom: true, noun: 'titles' };
  if (field.type === 'themes')  return { items: THEMES_CATALOG, allowCustom: true, noun: 'topics' };
  if (field.type === 'persona') return { items: PERSONAS.map(p => p.name), allowCustom: false, noun: 'personas' };
  if (field.type === 'target')  return { items: TARGETS.map(t => t.name).slice().sort((a,b) => a.localeCompare(b)), allowCustom: false, noun: 'targets' };
  return { items: [], allowCustom: false, noun: 'items' };
}

/* ---- tag chips ----------------------------------------------------------- */
function TagChips({ arr, mono, onRemove }) {
  return (
    <div className="ve-tag-chips" data-role="tag-chips">
      {arr.map(t => (
        <span className={cx('ve-tag-chip', mono && 'mono')} key={t}>
          <span>{String(t)}</span>
          <button
            aria-label="Remove"
            onMouseDown={(e) => { e.preventDefault(); e.stopPropagation(); onRemove(t); }}
          >×</button>
        </span>
      ))}
    </div>
  );
}

/* ---- per-type editors ----------------------------------------------------- */

function TextValueEditor({ filter, field, commit }) {
  const v = filter.value || '';
  const [hi, setHi] = React.useState(-1);
  const all = uniqueValuesFor(field);
  const q = String(v).toLowerCase();
  const matches = (q ? all.filter(x => x.toLowerCase().includes(q)) : all).slice(0, 8);
  const ctx = facetContextFor(field);
  return (
    <div className="ve-combo">
      <input
        className="ve-input" type="text" autoComplete="off" data-role="value"
        placeholder={`Type or pick a ${field.label.toLowerCase()}…`}
        value={v}
        autoFocus
        onChange={(e) => { setHi(-1); updateFilter(filter.id, { value: e.target.value }); }}
        onKeyDown={(e) => {
          if (e.key === 'ArrowDown' && matches.length) { e.preventDefault(); setHi(Math.min(matches.length - 1, hi + 1)); return; }
          if (e.key === 'ArrowUp' && matches.length)   { e.preventDefault(); setHi(Math.max(0, hi - 1)); return; }
          if (e.key === 'Enter') {
            e.preventDefault();
            if (matches[hi]) updateFilter(filter.id, { value: matches[hi] });
            closeEditor();
          }
        }}
      />
      <div className="ve-combo-list" data-role="combo-list">
        {matches.length === 0
          ? <div className="ve-combo-empty">No matches. Press Enter to use "{v}" anyway.</div>
          : matches.map((m, i) => (
              <button
                key={m}
                className={cx('ve-combo-item', i === hi && 'active')}
                onMouseDown={(e) => { e.preventDefault(); e.stopPropagation(); commit(m); }}
              >
                <span><HighlightMatch text={m} query={v} /></span>
                <ComboCount n={facetCountForOption(ctx, field, m)} />
              </button>
            ))}
      </div>
    </div>
  );
}

function SelectValueEditor({ filter, field }) {
  const ctx = facetContextFor(field);
  return (
    <div className="ve-pill-row">
      {field.options.map(o => (
        <button
          key={o}
          className={cx('ve-pill', filter.value === o && 'on')}
          onClick={() => updateFilter(filter.id, { value: filter.value === o ? '' : o })}
        ><span>{o}</span><PillCount n={facetCountForOption(ctx, field, o)} /></button>
      ))}
    </div>
  );
}

function MultiValueEditor({ filter, field }) {
  const ctx = facetContextFor(field);
  const cur = normalizeMulti(filter.value);
  const cycle = (v) => {
    const inInc = cur.include.includes(v);
    const inExc = cur.exclude.includes(v);
    let include = cur.include, exclude = cur.exclude;
    if (!inInc && !inExc) include = [...include, v];
    else if (inInc) { include = include.filter(x => x !== v); exclude = [...exclude, v]; }
    else exclude = exclude.filter(x => x !== v);
    updateFilter(filter.id, { value: { include, exclude } });
  };
  const showMine = field.id === 'account.territory'
    && typeof userOwnsTerritory === 'function' && typeof currentIdentity === 'function'
    && userOwnsTerritory(currentIdentity().id);
  const mineLabel = 'My Territory';
  const opts = showMine ? field.options.filter(o => o !== mineLabel) : field.options;
  return (
    <React.Fragment>
      <div className="ve-pill-row">
        {showMine ? (
          <button
            className={cx('ve-pill', 've-pill-mine',
              cur.include.includes(mineLabel) ? 'on' : (cur.exclude.includes(mineLabel) ? 'excluded' : ''))}
            onClick={() => cycle(mineLabel)}
          ><span><Icon name="user-round" size={13} />{mineLabel}</span></button>
        ) : null}
        {opts.map(o => (
          <button
            key={o}
            className={cx('ve-pill', cur.include.includes(o) ? 'on' : (cur.exclude.includes(o) ? 'excluded' : ''))}
            onClick={() => cycle(o)}
          ><span>{o}</span><PillCount n={facetCountForOption(ctx, field, o)} /></button>
        ))}
      </div>
      <div className="ve-hint">Click a value to <strong>include</strong>, click again to <strong>exclude</strong>, click again to clear.</div>
    </React.Fragment>
  );
}

function TerritoryEmptyEditor({ filter }) {
  return (
    <div className="ve-territory-empty">
      <img className="ofe-illustration" src={(window.__resources&&window.__resources.dioramaUndeveloped)||"assets/diorama-development-undeveloped.svg"} alt="" width="150" height="102" />
      <div className="ve-territory-empty-text"><strong>No territories yet</strong>Define your territory to filter by it — just like on your Account page.</div>
      <button
        className="btn-secondary-cta ve-territory-define" type="button"
        onClick={() => {
          if (typeof openFilterEditorModal !== 'function') return;
          const hide = (typeof accountTerritoryHideSet === 'function') ? accountTerritoryHideSet() : null;
          closeEditor({ keepIncomplete: true });
          openFilterEditorModal({
            title: 'Set my territory',
            subtitle: 'Define your patch — geographic (state, region) or account-based (profile, account type, size). It becomes a territory you can filter by.',
            saveLabel: 'Save territory',
            entity: 'accounts',
            filters: [],
            hideField: (f) => f.id === 'account.territory' || (hide ? hide.has(f.id) : (f.group !== 'Account' && f.group !== 'Location')),
            onSave: (defined) => {
              if (!defined.length) return;
              const name = 'My Territory';
              if (typeof setManualTerritory === 'function' && typeof currentIdentity === 'function') {
                setManualTerritory(currentIdentity().id, defined.map(f => ({ id: uid(), fieldId: f.fieldId, op: f.op, value: deepClone(f.value) })));
              }
              if (typeof seedAccountFromIdentity === 'function') { state.account = null; seedAccountFromIdentity(); }
              updateFilter(filter.id, { value: { include: [name], exclude: [] } });
              if (typeof showToast === 'function') showToast({ message: 'Your territory is set', icon: 'land-plot' });
            },
          });
        }}
      >
        <span className="icon-frame"><Icon name="map-pin" size={16} /></span>Set My Territory
      </button>
    </div>
  );
}

function NumberValueEditor({ filter }) {
  if (filter.op === 'between') {
    const [lo, hi] = Array.isArray(filter.value) ? filter.value : ['', ''];
    return (
      <div className="ve-between-row">
        <input className="ve-input" type="number" placeholder="min" value={lo} data-role="value-lo" autoFocus
          onChange={(e) => updateFilter(filter.id, { value: [e.target.value, hi] })} />
        <span style={{ color: 'var(--muted-foreground)' }}>—</span>
        <input className="ve-input" type="number" placeholder="max" value={hi} data-role="value-hi"
          onChange={(e) => updateFilter(filter.id, { value: [lo, e.target.value] })} />
      </div>
    );
  }
  return (
    <input className="ve-input" type="number" placeholder="Enter a number…" value={filter.value || ''} data-role="value" autoFocus
      onChange={(e) => updateFilter(filter.id, { value: e.target.value })}
      onKeyDown={(e) => { if (e.key === 'Enter') closeEditor(); }} />
  );
}

function TagComboEditor({ filter, field }) {
  // themes / persona / target — tag combobox over a catalog.
  const [q, setQ] = React.useState('');
  const [hi, setHi] = React.useState(-1);
  const arr = Array.isArray(filter.value) ? filter.value : [];
  const cat = presetCatalog(field);
  const ql = q.toLowerCase().trim();
  const all = cat.items.filter(t => !arr.includes(t));
  const matches = (ql ? all.filter(t => t.toLowerCase().includes(ql)) : all).slice(0, 20);
  const ctx = facetContextFor(field);
  const isTitles = field.catalog === 'titles';
  const placeholder =
    isTitles                 ? (arr.length ? 'Add another title…'   : 'Type a title — e.g. "VP Finance"') :
    field.type === 'themes'  ? (arr.length ? 'Add another topic…'   : 'Type a topic — e.g. "Budget for school laptops"') :
    field.type === 'persona' ? (arr.length ? 'Add another persona…' : 'Type a persona — e.g. "IT Decision Makers"') :
                               (arr.length ? 'Add another profile…' : 'Type a profile — e.g. "Large K-12 districts"');
  const hint =
    isTitles                 ? 'Matches contacts whose title contains any of these phrases. Add multiple to widen the search.' :
    field.type === 'themes'  ? 'Matches signals whose underlying documents touch on any of these topics.' :
    field.type === 'persona' ? <span>Each persona bundles title, department, seniority and confidence criteria. Multiple picks match contacts that satisfy <em>any</em> of them.</span> :
                               <span>Each profile bundles type, budget, location, signals and data-source criteria. Multiple picks match accounts that satisfy <em>any</em> of them.</span>;
  const addTag = (t) => {
    if (!arr.includes(t)) updateFilter(filter.id, { value: [...arr, t] });
    setQ('');
    setHi(-1);
  };
  return (
    <div className="ve-tag-area">
      <TagChips arr={arr} mono={false} onRemove={(t) => updateFilter(filter.id, { value: arr.filter(x => x !== t) })} />
      <input
        className="ve-input" type="text" autoComplete="off" data-role="value"
        placeholder={placeholder} value={q} autoFocus
        onChange={(e) => { setQ(e.target.value); setHi(-1); }}
        onKeyDown={(e) => {
          if (e.key === 'ArrowDown' && matches.length) { e.preventDefault(); setHi(Math.min(matches.length - 1, hi + 1)); return; }
          if (e.key === 'ArrowUp' && matches.length)   { e.preventDefault(); setHi(Math.max(0, hi - 1)); return; }
          if (e.key === 'Enter') {
            e.preventDefault();
            let pick = '';
            if (matches[hi]) pick = matches[hi];
            else if (q.trim() && cat.allowCustom) pick = q.trim();
            if (pick) addTag(pick);
            return;
          }
          if (e.key === 'Backspace' && q === '' && arr.length) {
            e.preventDefault();
            updateFilter(filter.id, { value: arr.slice(0, -1) });
          }
        }}
      />
      <div className="ve-combo-list" data-role="combo-list">
        {matches.length === 0
          ? (ql && cat.allowCustom
              ? <button className="ve-combo-item" onMouseDown={(e) => { e.preventDefault(); e.stopPropagation(); addTag(ql); }}>
                  Use <em>"{ql}"</em> as a custom topic
                </button>
              : <div className="ve-combo-empty">{ql ? `No ${cat.noun} match "${ql}".` : `All ${cat.noun} selected.`}</div>)
          : matches.map((m, i) => (
              <button
                key={m}
                className={cx('ve-combo-item', i === hi && 'active')}
                onMouseDown={(e) => { e.preventDefault(); e.stopPropagation(); addTag(m); }}
              >
                <span><HighlightMatch text={m} query={q} /></span>
                <ComboCount n={facetCountForOption(ctx, field, m)} />
              </button>
            ))}
      </div>
      <div className="ve-hint">{hint}</div>
    </div>
  );
}

function EntityValueEditor({ filter, field, commit }) {
  const v = filter.value || '';
  const [hi, setHi] = React.useState(-1);
  const ql = String(v).toLowerCase().trim();
  const matches = (ql ? COMPETITORS_CATALOG.filter(c => c.toLowerCase().includes(ql)) : COMPETITORS_CATALOG).slice(0, 8);
  const ctx = facetContextFor(field);
  return (
    <div className="ve-combo">
      <input
        className="ve-input" type="text" autoComplete="off" data-role="value"
        placeholder="Type a vendor name…" value={v} autoFocus
        onChange={(e) => { setHi(-1); updateFilter(filter.id, { value: e.target.value }); }}
        onKeyDown={(e) => {
          if (e.key === 'ArrowDown' && matches.length) { e.preventDefault(); setHi(Math.min(matches.length - 1, hi + 1)); return; }
          if (e.key === 'ArrowUp' && matches.length)   { e.preventDefault(); setHi(Math.max(0, hi - 1)); return; }
          if (e.key === 'Enter') {
            e.preventDefault();
            if (matches[hi]) updateFilter(filter.id, { value: matches[hi] });
            else if (String(v).trim()) updateFilter(filter.id, { value: String(v).trim() });
            closeEditor();
          }
        }}
      />
      <div className="ve-combo-list" data-role="combo-list">
        {matches.length === 0
          ? <button className="ve-combo-item" onMouseDown={(e) => { e.preventDefault(); e.stopPropagation(); commit(ql); }}>
              Use <em>"{ql}"</em> as a custom entity
            </button>
          : matches.map((m, i) => (
              <button
                key={m}
                className={cx('ve-combo-item', i === hi && 'active')}
                onMouseDown={(e) => { e.preventDefault(); e.stopPropagation(); commit(m); }}
              >
                <span><HighlightMatch text={m} query={v} /></span>
                <ComboCount n={facetCountForOption(ctx, field, m)} />
              </button>
            ))}
      </div>
    </div>
  );
}

function TokensValueEditor({ filter }) {
  const [q, setQ] = React.useState('');
  const arr = Array.isArray(filter.value) ? filter.value : [];
  return (
    <div className="ve-tag-area">
      <TagChips arr={arr} mono onRemove={(t) => updateFilter(filter.id, { value: arr.filter(x => x !== t) })} />
      <input
        className="ve-input" type="text" autoComplete="off" data-role="value"
        placeholder={arr.length ? 'Add another keyword…' : 'Type a keyword, press Enter'}
        value={q} autoFocus
        onChange={(e) => setQ(e.target.value)}
        onKeyDown={(e) => {
          if (e.key === 'Enter' || e.key === ',') {
            e.preventDefault();
            const tok = q.trim().replace(/,$/, '');
            if (tok && !arr.includes(tok)) updateFilter(filter.id, { value: [...arr, tok] });
            setQ('');
            return;
          }
          if (e.key === 'Backspace' && q === '' && arr.length) {
            e.preventDefault();
            updateFilter(filter.id, { value: arr.slice(0, -1) });
          }
        }}
      />
      <div className="ve-hint">Tokenized search across state, account name, department, title, contact name &amp; signal documents — weighted &amp; ranked. Enter or comma to add.</div>
    </div>
  );
}

function SourceValueEditor({ filter, field }) {
  const v = (filter.value && typeof filter.value === 'object') ? filter.value : {};
  const writeSel = (key, val) => {
    const nv = { ...v };
    if (val === '') delete nv[key];
    else nv[key] = val;
    updateFilter(filter.id, { value: nv });
  };
  const writeRange = (key, bound, val) => {
    const nv = { ...v };
    const r = { ...(nv[key] || {}) };
    r[bound] = val === '' ? '' : Number(val);
    if ((r.min === '' || r.min == null) && (r.max === '' || r.max == null)) delete nv[key];
    else nv[key] = r;
    updateFilter(filter.id, { value: nv });
  };
  return (
    <div className="ve-source" data-role="source-form">
      {(field.sections || []).map((sec, si) => (
        <div className="ve-source-section" key={si}>
          {sec.title ? <div className="ve-source-section-title">{sec.title}</div> : null}
          {sec.fields.map(sf => {
            if (sf.type === 'select') {
              const cur = v[sf.key] == null ? '' : String(v[sf.key]);
              return (
                <div className="ve-source-field" key={sf.key}>
                  <label className="ve-source-label">{sf.label}</label>
                  <select className="ve-source-select" value={cur} onChange={(e) => writeSel(sf.key, e.target.value)}>
                    <option value="">{sf.placeholder || 'Select…'}</option>
                    {(sf.options || []).map(o => <option key={o} value={String(o)}>{o}</option>)}
                  </select>
                </div>
              );
            }
            if (sf.type === 'range') {
              const cur = v[sf.key] || {};
              return (
                <div className="ve-source-field" key={sf.key}>
                  <label className="ve-source-label">{sf.label}</label>
                  <div className="ve-source-range">
                    <span className="ve-source-rlabel">Min</span>
                    <input type="number" className="ve-source-rinput" placeholder="Enter minimum" step={sf.step || undefined}
                      value={cur.min != null ? cur.min : ''} onChange={(e) => writeRange(sf.key, 'min', e.target.value)} />
                    <span className="ve-source-rlabel">Max</span>
                    <input type="number" className="ve-source-rinput" placeholder="Enter maximum" step={sf.step || undefined}
                      value={cur.max != null ? cur.max : ''} onChange={(e) => writeRange(sf.key, 'max', e.target.value)} />
                  </div>
                </div>
              );
            }
            return null;
          })}
        </div>
      ))}
    </div>
  );
}

/* ---- the popover ---------------------------------------------------------- */

function ValueEditorBody({ filter, field }) {
  const commitAndClose = (val) => {
    updateFilter(filter.id, { value: val });
    setTimeout(() => closeEditor(), 0);
  };
  if (field.type === 'text')   return <TextValueEditor filter={filter} field={field} commit={commitAndClose} />;
  if (field.type === 'select') return <SelectValueEditor filter={filter} field={field} />;
  if (field.type === 'multi') {
    const noTerritories = field.id === 'account.territory' && (!field.options || field.options.length === 0)
      && !(typeof userOwnsTerritory === 'function' && typeof currentIdentity === 'function' && userOwnsTerritory(currentIdentity().id));
    if (noTerritories) return <TerritoryEmptyEditor filter={filter} />;
    return <MultiValueEditor filter={filter} field={field} />;
  }
  if (field.type === 'number') return <NumberValueEditor filter={filter} />;
  if (field.type === 'themes' || field.type === 'persona' || field.type === 'target') {
    return <TagComboEditor filter={filter} field={field} />;
  }
  if (field.type === 'entity') return <EntityValueEditor filter={filter} field={field} commit={commitAndClose} />;
  if (field.type === 'tokens') return <TokensValueEditor filter={filter} />;
  if (field.type === 'source') return <SourceValueEditor filter={filter} field={field} />;
  return null;
}

function EditorPopover() {
  useAppState();
  const ref = React.useRef(null);
  const st = fpState();
  const filter = st.openFieldId ? st.filters.find(f => f.fieldId === st.openFieldId) : null;
  const field = filter ? FIELD_BY_ID[filter.fieldId] : null;

  // Position relative to the live anchor (re-resolved each layout, since
  // React re-renders replace the field-list nodes).
  React.useLayoutEffect(() => {
    const pop = ref.current;
    if (!pop || !filter) return;
    const position = () => {
      let anchor = document.querySelector(`.fl-item[data-field-id="${filter.fieldId}"]`)
        || document.querySelector(`.filter-chip[data-field-id="${filter.fieldId}"]`)
        || st.editorAnchor;
      if (!anchor || !document.body.contains(anchor)) {
        pop.dataset.arrow = 'none';
        pop.style.left = '50%';
        pop.style.top = '120px';
        pop.style.transform = 'translateX(-50%)';
        return;
      }
      pop.style.transform = '';
      const r = anchor.getBoundingClientRect();
      const popW = pop.offsetWidth;
      const popH = pop.offsetHeight;
      const W = window.innerWidth, H = window.innerHeight, margin = 12;
      const isFieldListItem = !!anchor.closest('.fw-items, .fp-field-list');
      let left = isFieldListItem ? r.left + 32 : r.left;
      let top = r.bottom + 10;
      if (left + popW > W - margin) left = W - popW - margin;
      if (left < margin) left = margin;
      if (top + popH > H - margin) top = Math.max(margin, H - popH - margin);
      if (top < margin) top = margin;
      pop.style.left = left + 'px';
      pop.style.top = top + 'px';
      pop.dataset.arrow = 'top';
    };
    position();
    window.addEventListener('resize', position);
    const panel = document.querySelector('.content-panel');
    if (panel) panel.addEventListener('scroll', position, { passive: true });
    return () => {
      window.removeEventListener('resize', position);
      if (panel) panel.removeEventListener('scroll', position);
    };
  });

  // Outside click + Escape.
  React.useEffect(() => {
    if (!filter) return;
    const onDown = (e) => {
      const pop = ref.current;
      if (!pop) return;
      if (pop.contains(e.target)) return;
      if (e.target.closest('.fl-item')) return;
      if (e.target.closest('.filter-chip')) return;
      if (e.target.closest('.ep-sugg')) return;
      closeEditor();
    };
    const onKey = (e) => {
      if (e.key === 'Escape') { e.preventDefault(); closeEditor(); }
    };
    document.addEventListener('mousedown', onDown);
    document.addEventListener('keydown', onKey);
    return () => {
      document.removeEventListener('mousedown', onDown);
      document.removeEventListener('keydown', onKey);
    };
  }, [filter ? filter.id : null]);

  if (!filter || !field) return null;

  const related = field.entity !== st.entity && !(field.group === 'Signals' || field.group === 'Data Sources');
  const sourceMode = field.type === 'source';
  const complete = isFilterComplete(filter);
  const applyEnabled = sourceMode ? true : complete;
  const applyLabel = sourceMode
    ? (filter.value && filter.value._committed ? 'Done' : 'Apply')
    : (complete ? 'Done' : 'Apply');
  const showOp = field.type !== 'multi';
  const ops = OPS[field.type] || [];
  const nSource = sourceMode ? countSetSubvalues(field, filter.value || {}) : 0;

  return (
    <div className="editor-popover" ref={ref} data-filter-id={filter.id}>
      <div className={cx('value-editor', sourceMode && 've-mode-source')} data-role="editor" data-filter-id={filter.id}>
        <div className="ve-head">
          <span className="ve-glyph"><Icon name={field.icon} size={13} /></span>
          <span className="ve-title">
            <span className="ve-prefix">{related ? `via ${ENTITY[field.entity].singular} ·` : 'Filter by'}</span>
            {displayLabel(field, st.entity)}
          </span>
          {showOp ? (
            <select
              className="ve-op" data-role="op" value={filter.op}
              onChange={(e) => updateFilter(filter.id, { op: e.target.value, value: defaultValue(field) })}
            >
              {ops.map(o => <option key={o.id} value={o.id}>{o.label}</option>)}
            </select>
          ) : null}
          <span className="ve-spacer"></span>
          <button className="ve-cancel" onClick={() => removeFilter(filter.id)}>Remove</button>
          <button
            className="ve-done" disabled={!applyEnabled}
            onClick={() => {
              if (sourceMode) {
                updateFilter(filter.id, { value: { ...(filter.value || {}), _committed: true } });
                closeEditor();
                return;
              }
              if (isFilterComplete(filter)) closeEditor();
            }}
          >{applyLabel}</button>
        </div>
        {sourceMode ? (
          <div className="ve-source-caption">
            {nSource > 0 ? `${nSource} condition${nSource === 1 ? '' : 's'} set` : `No conditions — will match any record from ${field.label}.`}
          </div>
        ) : null}
        <ValueEditorBody filter={filter} field={field} />
      </div>
    </div>
  );
}

Object.assign(window, { EditorPopover, facetContextFor, facetCountForOption, uniqueValuesFor, presetCatalog, HighlightMatch, ComboCount, PillCount });
