// cmdk.jsx — global ⌘K search palette.
//
// Wired by `shell.jsx`'s topbar; opens from anywhere via ⌘K / Ctrl+K.
// Hits the workspace-scoped /search endpoint with optional cross-workspace
// scope (when the JWT user belongs to multiple workspaces). Result groups
// are Calls / Numbers / Campaigns / Transcripts, capped server-side.
//
// Keyboard:
//   ⌘K / Ctrl+K  open
//   Esc          close
//   ↑ / ↓        move selection
//   Enter        open selected
//   ⌘+Enter      open in new tab
//
// "Pre-fill chip strip from query" is intentionally not implemented in
// v1 — the chips on the calls page are URL-driven, so opening a saved
// link replicates the same effect with zero extra UI surface.

(function () {
  const { useState, useEffect, useRef, useMemo, useCallback } = React;

  function CmdK({ open, onClose, workspace, navigate }) {
    const wid = workspace?.id;
    const [q, setQ] = useState("");
    const [scope, setScope] = useState("workspace"); // 'workspace' | 'user'
    const [results, setResults] = useState({ calls: [], numbers: [], campaigns: [], transcripts: [] });
    const [loading, setLoading] = useState(false);
    const [active, setActive] = useState(0);
    const inputRef = useRef(null);

    // Reset on open
    useEffect(() => {
      if (open) {
        setQ("");
        setResults({ calls: [], numbers: [], campaigns: [], transcripts: [] });
        setActive(0);
        // Defer focus so the input is mounted
        setTimeout(() => inputRef.current?.focus(), 0);
      }
    }, [open]);

    // Debounced search
    useEffect(() => {
      if (!open) return;
      if (q.trim().length < 2) {
        setResults({ calls: [], numbers: [], campaigns: [], transcripts: [] });
        return;
      }
      const handle = setTimeout(async () => {
        setLoading(true);
        try {
          const res = await API.search.query(wid, q.trim(), { scope });
          // The shared API client always wraps responses as { data, meta }.
          // Tolerate the unwrapped shape too in case a caller pre-unwraps.
          const data = res?.data ?? res ?? {};
          setResults({
            calls: data.calls || [],
            numbers: data.numbers || [],
            campaigns: data.campaigns || [],
            transcripts: data.transcripts || [],
          });
          setActive(0);
        } catch {
          // Silent — palette stays usable, results empty
        } finally {
          setLoading(false);
        }
      }, 180);
      return () => clearTimeout(handle);
    }, [q, scope, wid, open]);

    // Build a flat ordered list for keyboard navigation. Each entry
    // carries enough info to render its row + handle Enter.
    const rows = useMemo(() => {
      const out = [];
      const push = (group, item, action) => out.push({ group, item, action });
      for (const c of results.calls)       push("Calls",       c, () => navigate("call", c));
      for (const n of results.numbers)     push("Numbers",     n, () => navigate("number", n));
      for (const c of results.campaigns)   push("Campaigns",   c, () => navigate("campaign", c));
      for (const t of results.transcripts) push("Transcripts", t, () => navigate("call", { id: t.callId }));
      return out;
    }, [results, navigate]);

    const handleKey = useCallback((e) => {
      if (e.key === "ArrowDown") {
        e.preventDefault();
        setActive((i) => Math.min(rows.length - 1, i + 1));
      } else if (e.key === "ArrowUp") {
        e.preventDefault();
        setActive((i) => Math.max(0, i - 1));
      } else if (e.key === "Enter" && rows[active]) {
        e.preventDefault();
        rows[active].action();
        onClose();
      } else if (e.key === "Escape") {
        e.preventDefault();
        onClose();
      }
    }, [rows, active, onClose]);

    if (!open) return null;

    // Group rows for rendering, preserving the flat-index order so
    // ↑↓ navigation maps cleanly to the visible list.
    let cursor = 0;
    const groups = [];
    for (const groupName of ["Calls", "Numbers", "Campaigns", "Transcripts"]) {
      const items = rows.filter((r) => r.group === groupName);
      if (!items.length) continue;
      const startIdx = cursor;
      cursor += items.length;
      groups.push({ name: groupName, items, startIdx });
    }

    return (
      <div className="cmdk-overlay" onMouseDown={(e) => e.target === e.currentTarget && onClose()}>
        <div className="cmdk" onKeyDown={handleKey}>
          <div className="cmdk__input">
            <I.search size={16}/>
            <input
              ref={inputRef}
              value={q}
              onChange={(e) => setQ(e.target.value)}
              placeholder="Search calls, numbers, campaigns, transcripts…"
              aria-label="Global search"
            />
            <button
              className="cmdk__hint"
              title={scope === "workspace" ? "Searching this workspace only" : "Searching all your workspaces"}
              onClick={() => setScope(scope === "workspace" ? "user" : "workspace")}
              style={{background:"transparent", border:0, cursor:"pointer"}}
            >
              {scope === "user" ? "All workspaces" : "This workspace"}
            </button>
            <kbd>esc</kbd>
          </div>

          <div className="cmdk__results">
            {loading && q.length >= 2 && rows.length === 0 && (
              <div className="cmdk__empty">Searching…</div>
            )}
            {!loading && q.trim().length < 2 && (
              <div className="cmdk__empty">
                Type at least 2 characters.<br/>
                <span className="muted" style={{fontSize:11.5}}>
                  Try a phone number, caller name, campaign, or a word from a transcript.
                </span>
              </div>
            )}
            {!loading && q.trim().length >= 2 && rows.length === 0 && (
              <div className="cmdk__empty">No matches for "{q}"</div>
            )}

            {groups.map((g) => (
              <div key={g.name}>
                <div className="cmdk__group-label">{g.name}</div>
                {g.items.map((row, i) => {
                  const flatIdx = g.startIdx + i;
                  const on = flatIdx === active;
                  return (
                    <CmdKRow
                      key={`${g.name}-${i}`}
                      group={g.name}
                      item={row.item}
                      on={on}
                      scope={scope}
                      onMouseEnter={() => setActive(flatIdx)}
                      onClick={() => { row.action(); onClose(); }}
                    />
                  );
                })}
              </div>
            ))}
          </div>

          <div className="cmdk__footer">
            <span className="cmdk__hint"><kbd>↑↓</kbd> navigate · <kbd>↵</kbd> open · <kbd>esc</kbd> close</span>
            <span className="cmdk__hint">Powered by Postgres</span>
          </div>
        </div>
      </div>
    );
  }

  function CmdKRow({ group, item, on, scope, onMouseEnter, onClick }) {
    const showWs = scope === "user" && item.workspaceName;
    let icon, title, sub;
    if (group === "Calls") {
      icon = <I.phone size={13}/>;
      title = item.callerName || fmtNumber(item.callerNumber) || item.callerNumber;
      sub = `${item.numberName || fmtNumber(item.number) || ""} · ${item.status} · ${fmtRelative(item.startedAt)}`;
    } else if (group === "Numbers") {
      icon = <I.pin size={13}/>;
      title = item.friendlyName || fmtNumber(item.number);
      sub = item.friendlyName ? fmtNumber(item.number) : item.status;
    } else if (group === "Campaigns") {
      icon = <I.route size={13}/>;
      title = item.name;
      sub = item.channel || "";
    } else if (group === "Transcripts") {
      icon = <I.mic size={13}/>;
      title = item.callerName || fmtNumber(item.callerNumber) || "Call";
      // snippet is HTML (ts_headline returns <mark>…</mark>) — render as such
      sub = <span dangerouslySetInnerHTML={{ __html: item.snippet || "" }}/>;
    }
    return (
      <button
        className={"cmdk__row" + (on ? " cmdk__row--on" : "")}
        onMouseEnter={onMouseEnter}
        onClick={onClick}
      >
        <span className="cmdk__row__icon">{icon}</span>
        <span className="cmdk__row__main">
          <span className="cmdk__row__title">{title}</span>
          <span className="cmdk__row__sub">{sub}</span>
        </span>
        {showWs && <span className="cmdk__row__ws">{item.workspaceName}</span>}
      </button>
    );
  }

  // Hook the rest of the app uses to mount + control the palette. Returns
  // { open(), close(), toggle(), Mounted }. The `Mounted` element is the
  // overlay portal — render it once at the top of <Workspace>.
  function useCmdK({ workspace, onNavigate }) {
    const [open, setOpen] = useState(false);

    // Global keyboard shortcut
    useEffect(() => {
      const onKey = (e) => {
        const isMac = navigator.platform.toUpperCase().indexOf("MAC") >= 0;
        const cmdK = (isMac ? e.metaKey : e.ctrlKey) && e.key.toLowerCase() === "k";
        if (cmdK) {
          e.preventDefault();
          setOpen((o) => !o);
        }
      };
      window.addEventListener("keydown", onKey);
      return () => window.removeEventListener("keydown", onKey);
    }, []);

    const Mounted = (
      <CmdK
        open={open}
        onClose={() => setOpen(false)}
        workspace={workspace}
        navigate={onNavigate}
      />
    );

    return { open: () => setOpen(true), close: () => setOpen(false), Mounted };
  }

  window.CmdK = CmdK;
  window.useCmdK = useCmdK;
})();
