// Searchable single/multi select for Bot Config form dropdowns.
// Babel CDN (no ES modules) — exported on window like shared.jsx.

const { useState, useEffect, useRef } = React;

function filterSelectOptions(items, query, fields) {
  const list = Array.isArray(items) ? items : [];
  const q = String(query || "").trim();
  if (!q) return list;
  if (typeof matchQuery === "function") return list.filter((item) => matchQuery(item, q, fields));
  const needle = q.toLowerCase();
  const fieldList = Array.isArray(fields) && fields.length ? fields : [];
  return list.filter((item) => fieldList.some((field) => {
    const raw = typeof field === "function" ? field(item) : (item == null ? undefined : item[field]);
    if (raw == null || typeof raw === "object") return false;
    return String(raw).toLowerCase().includes(needle);
  }));
}

function parseTimeMs(raw) {
  if (raw == null || raw === "") return null;
  const t = raw instanceof Date ? raw.getTime() : Date.parse(raw);
  return Number.isFinite(t) ? t : null;
}

function optionRecencyMs(item) {
  if (item == null || typeof item !== "object") return null;
  const fromStamp = parseTimeMs(item.updated_at)
    || parseTimeMs(item.updatedAt)
    || parseTimeMs(item.created_at)
    || parseTimeMs(item.createdAt);
  if (fromStamp != null) return fromStamp;
  const id = item._id;
  const hex = id && typeof id === "object" && typeof id.toString === "function" ? id.toString() : String(id || "");
  if (/^[a-fA-F0-9]{24}$/.test(hex)) {
    const t = parseInt(hex.slice(0, 8), 16) * 1000;
    if (Number.isFinite(t)) return t;
  }
  return null;
}

function compareIdDesc(a, b) {
  const aid = String(a && a._id != null ? a._id : "");
  const bid = String(b && b._id != null ? b._id : "");
  if (aid === bid) return 0;
  return aid < bid ? 1 : -1;
}

function sortSelectOptionsByRecent(items) {
  const list = Array.isArray(items) ? items.slice() : [];
  list.sort((a, b) => {
    const at = optionRecencyMs(a);
    const bt = optionRecencyMs(b);
    if (at == null && bt == null) return compareIdDesc(a, b);
    if (at == null) return 1;
    if (bt == null) return -1;
    if (bt !== at) return bt - at;
    return compareIdDesc(a, b);
  });
  return list;
}

function optionId(item, getId) {
  if (item == null) return "";
  return String(getId ? getId(item) : item._id);
}

function selectedIds(value, multiple) {
  if (multiple) return (Array.isArray(value) ? value : []).map(String);
  return value ? [String(value)] : [];
}

const SearchableSelect = ({
  items = [],
  value,
  onChange,
  multiple = false,
  placeholder = "Select…",
  searchPlaceholder = "Search…",
  getId = (item) => item._id,
  getLabel = (item) => item.name || "",
  getSub,
  searchFields,
  emptyText = "No options",
  noneLabel,
}) => {
  const [open, setOpen] = useState(false);
  const [query, setQuery] = useState("");
  const rootRef = useRef(null);
  const searchRef = useRef(null);

  const fields = searchFields || [getLabel, getSub].filter(Boolean);
  const ids = selectedIds(value, multiple);
  const idSet = new Set(ids);
  const ordered = sortSelectOptionsByRecent(items);
  const selectedItems = ordered.filter((item) => idSet.has(optionId(item, getId)));
  const visible = filterSelectOptions(ordered, query, fields);

  useEffect(() => {
    if (!open) return undefined;
    const onDoc = (e) => {
      if (rootRef.current && !rootRef.current.contains(e.target)) setOpen(false);
    };
    const onKey = (e) => {
      if (e.key === "Escape") setOpen(false);
    };
    document.addEventListener("mousedown", onDoc);
    document.addEventListener("keydown", onKey);
    return () => {
      document.removeEventListener("mousedown", onDoc);
      document.removeEventListener("keydown", onKey);
    };
  }, [open]);

  useEffect(() => {
    if (open) {
      setQuery("");
      setTimeout(() => searchRef.current && searchRef.current.focus(), 0);
    }
  }, [open]);

  const close = () => setOpen(false);

  const pick = (id) => {
    if (!onChange) return;
    if (!multiple) {
      onChange(id);
      close();
      return;
    }
    onChange(idSet.has(String(id)) ? ids.filter((x) => x !== String(id)) : [...ids, String(id)]);
  };

  const clearOne = (id) => {
    if (!onChange) return;
    if (multiple) onChange(ids.filter((x) => x !== String(id)));
    else onChange("");
  };

  const triggerLabel = (() => {
    if (!ids.length) return placeholder;
    if (!multiple) {
      const item = selectedItems[0];
      if (!item) return placeholder;
      const sub = getSub ? getSub(item) : "";
      return sub ? `${getLabel(item)} (${sub})` : getLabel(item);
    }
    if (selectedItems.length === 1) return getLabel(selectedItems[0]);
    return `${ids.length} selected`;
  })();

  return (
    <div className="ss" ref={rootRef}>
      <button
        type="button"
        className={`ss-trigger ${open ? "open" : ""} ${ids.length ? "" : "placeholder"}`}
        onClick={() => setOpen((v) => !v)}
        aria-haspopup="listbox"
        aria-expanded={open}
      >
        <span className="ss-trigger-label">{triggerLabel}</span>
        <I.ChevronDown size={14} />
      </button>

      {open && (
        <div className="ss-pop">
          <div className="ss-search">
            <I.Search size={13} />
            <input
              ref={searchRef}
              className="input"
              value={query}
              onChange={(e) => setQuery(e.target.value)}
              placeholder={searchPlaceholder}
              aria-label={searchPlaceholder}
            />
          </div>
          <div className="ss-list" role="listbox" aria-multiselectable={multiple || undefined}>
            {!multiple && noneLabel && !query.trim() && (
              <button
                type="button"
                className={`ss-opt ${!ids.length ? "selected" : ""}`}
                onClick={() => { onChange(""); close(); }}
              >
                <span className="ss-opt-main">{noneLabel}</span>
              </button>
            )}
            {visible.map((item) => {
              const id = optionId(item, getId);
              const selected = idSet.has(id);
              const sub = getSub ? getSub(item) : "";
              return (
                <button
                  type="button"
                  key={id}
                  className={`ss-opt ${selected ? "selected" : ""}`}
                  role="option"
                  aria-selected={selected}
                  onClick={() => pick(id)}
                >
                  {multiple && (
                    <input type="checkbox" checked={selected} readOnly tabIndex={-1} />
                  )}
                  <span className="ss-opt-text">
                    <span className="ss-opt-main">{getLabel(item)}</span>
                    {sub ? <span className="ss-opt-sub mono muted">{sub}</span> : null}
                  </span>
                  {!multiple && selected ? <I.Check size={13} /> : null}
                </button>
              );
            })}
            {visible.length === 0 && (
              <div className="ss-empty muted">{items.length === 0 ? emptyText : "No matches"}</div>
            )}
          </div>
        </div>
      )}

      {multiple && selectedItems.length > 0 && (
        <div className="ss-chips">
          {selectedItems.map((item) => {
            const id = optionId(item, getId);
            return (
              <span key={id} className="ss-chip">
                <span className="ss-chip-label">{getLabel(item)}</span>
                <button type="button" className="ss-chip-x" onClick={() => clearOne(id)} aria-label={`Remove ${getLabel(item)}`}>
                  <I.X size={11} />
                </button>
              </span>
            );
          })}
        </div>
      )}
    </div>
  );
};

Object.assign(window, { filterSelectOptions, sortSelectOptionsByRecent, SearchableSelect });
