/* Overlays: command palette (fuzzy file finder), content search, context menu, toast */

function fuzzy(q, str) {
  q = q.toLowerCase(); const s = str.toLowerCase();
  let i = 0; const idx = [];
  for (let j = 0; j < s.length && i < q.length; j++) {
    if (s[j] === q[i]) { idx.push(j); i++; }
  }
  return i === q.length ? idx : null;
}

function Highlight({ text, idx }) {
  if (!idx || !idx.length) return <span>{text}</span>;
  const set = new Set(idx);
  return <span>{text.split("").map((ch, i) => set.has(i) ? <b key={i}>{ch}</b> : <React.Fragment key={i}>{ch}</React.Fragment>)}</span>;
}

function SearchModal({ onOpen, onOpenAt, onClose, changeSet }) {
  const [q, setQ] = useState("");
  const [sel, setSel] = useState(0);
  const inputRef = useRef(null);
  const leftRef = useRef(null);
  const allPaths = useMemo(() => Object.keys(PROJECT.files), []);

  useEffect(() => { inputRef.current && inputRef.current.focus(); }, []);

  // content hits (left)
  const content = useMemo(() => {
    const term = q.trim();
    if (term.length < 2) return [];
    const low = term.toLowerCase();
    const groups = [];
    for (const [path, src] of Object.entries(PROJECT.files)) {
      const lines = src.split("\n");
      const hits = [];
      lines.forEach((ln, i) => {
        const ix = ln.toLowerCase().indexOf(low);
        if (ix >= 0) hits.push({ no: i + 1, ln, ix });
      });
      if (hits.length) groups.push({ path, hits });
    }
    return groups;
  }, [q]);

  // file-name matches (right)
  const files = useMemo(() => {
    const term = q.trim();
    if (!term) return [];
    const out = [];
    for (const p of allPaths) {
      const name = p.split("/").pop();
      const ni = fuzzy(term, name);
      if (ni) { out.push({ path: p, idx: ni, rank: 0, pos: ni[0] }); continue; }
      const pi = fuzzy(term, p);
      if (pi) out.push({ path: p, idx: null, rank: 1, pos: pi[0] });
    }
    out.sort((a, b) => a.rank - b.rank || a.pos - b.pos || a.path.length - b.path.length);
    return out;
  }, [q]);

  // flat list of content hits for keyboard nav
  const flat = useMemo(() => {
    const arr = [];
    content.forEach((g) => g.hits.forEach((h) => arr.push({ path: g.path, no: h.no })));
    return arr;
  }, [content]);

  const totalHits = flat.length;
  useEffect(() => { setSel(0); }, [q]);
  useEffect(() => {
    const el = leftRef.current && leftRef.current.querySelector(".sr-line.sel");
    if (el) el.scrollIntoView({ block: "nearest" });
  }, [sel]);

  function onKey(e) {
    if (e.key === "ArrowDown") { e.preventDefault(); setSel((s) => Math.min(s + 1, flat.length - 1)); }
    else if (e.key === "ArrowUp") { e.preventDefault(); setSel((s) => Math.max(s - 1, 0)); }
    else if (e.key === "Enter") {
      e.preventDefault();
      if (flat[sel]) { onOpenAt(flat[sel].path, flat[sel].no); onClose(); }
      else if (files[0]) { onOpen(files[0].path); onClose(); }
    } else if (e.key === "Escape") { e.preventDefault(); onClose(); }
  }

  function renderLine(ln, ix, len) {
    const pre = ln.slice(0, ix), mid = ln.slice(ix, ix + len), post = ln.slice(ix + len);
    return <span className="tx">{pre}<mark>{mid}</mark>{post}</span>;
  }

  const term = q.trim();
  let flatIx = -1;

  return (
    <div className="scrim" onMouseDown={onClose}>
      <div className="search-modal" onMouseDown={(e) => e.stopPropagation()}>
        <div className="pi">
          {Icon.search({ style: { color: "var(--fg-3)" } })}
          <input ref={inputRef} value={q} onChange={(e) => setQ(e.target.value)} onKeyDown={onKey}
            placeholder="Search content and file names…" spellCheck={false} />
          <span className="mode-chip">{totalHits} hit{totalHits === 1 ? "" : "s"} · {files.length} file{files.length === 1 ? "" : "s"}</span>
        </div>
        <div className="search-cols">
          <div className="sc-left" ref={leftRef}>
            <div className="sc-head">Content {totalHits > 0 && <span className="sc-ct">{totalHits}</span>}</div>
            {term.length < 2 && <div className="pempty">Type at least 2 characters</div>}
            {term.length >= 2 && content.length === 0 && <div className="pempty">No content matches</div>}
            {content.map((g) => (
              <React.Fragment key={g.path}>
                <div className="sr-file" onClick={() => onOpenAt(g.path, g.hits[0].no)}>
                  <FileIcon path={g.path} />
                  <span className="srf-name">{g.path}</span>
                  <span className="cnt">{g.hits.length}</span>
                </div>
                {g.hits.slice(0, 12).map((h) => {
                  flatIx++;
                  const me = flatIx;
                  return (
                    <div key={h.no} className={"sr-line" + (me === sel ? " sel" : "")}
                      onMouseEnter={() => setSel(me)}
                      onClick={() => { onOpenAt(g.path, h.no); onClose(); }}>
                      <span className="no">{h.no}</span>
                      {renderLine(h.ln, h.ix, term.length)}
                    </div>
                  );
                })}
              </React.Fragment>
            ))}
          </div>
          <div className="sc-right">
            <div className="sc-head">Files {files.length > 0 && <span className="sc-ct">{files.length}</span>}</div>
            {!term && <div className="pempty sm">Start typing…</div>}
            {term && files.length === 0 && <div className="pempty sm">No file names match</div>}
            {files.slice(0, 40).map((r) => {
              const name = r.path.split("/").pop();
              const dir = r.path.split("/").slice(0, -1).join("/");
              return (
                <div key={r.path} className="fres" onClick={() => { onOpen(r.path); onClose(); }} title={r.path}>
                  <FileIcon path={r.path} />
                  <div className="fres-txt">
                    <span className="fn"><Highlight text={name} idx={r.idx} /></span>
                    {dir && <span className="fd">{dir}/</span>}
                  </div>
                  {changeSet.has(r.path) && <span className="tree-badge M" style={{ fontFamily: "var(--mono)", fontSize: 10 }}>•</span>}
                </div>
              );
            })}
          </div>
        </div>
      </div>
    </div>
  );
}

function ContextMenu({ menu, onClose }) {
  const ref = useRef(null);
  useEffect(() => {
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) onClose(); };
    const k = (e) => { if (e.key === "Escape") onClose(); };
    document.addEventListener("mousedown", h);
    document.addEventListener("keydown", k);
    return () => { document.removeEventListener("mousedown", h); document.removeEventListener("keydown", k); };
  }, []);
  if (!menu) return null;
  const x = Math.min(menu.x, window.innerWidth - 270);
  const y = Math.min(menu.y, window.innerHeight - (menu.items.length * 34 + 60));
  return (
    <div className="ctx" ref={ref} style={{ left: x, top: y }}>
      {menu.note && <div className="ctx-note">{menu.note}</div>}
      {menu.items.map((it, i) => it.sep ? <div key={i} className="ctx-sep" /> : (
        <div key={i} className={"ctx-item" + (it.primary ? " primary" : "")}
          onClick={() => { it.onClick(); onClose(); }}>
          <span className="ic">{it.icon}</span>
          <span>{it.label}</span>
          {it.kbd && <span className="kc">{it.kbd}</span>}
        </div>
      ))}
    </div>
  );
}

function Toasts({ toasts }) {
  return (
    <div className="toast-wrap">
      {toasts.map((t) => (
        <div key={t.id} className="toast">
          {Icon.copy({ style: { color: "var(--accent)" } })}
          <span className="tt">{t.title}</span>
          {t.ref && <span className="tref">{t.ref}</span>}
        </div>
      ))}
    </div>
  );
}

function PassPopup({ x, y, refStr, onConfirm, onCancel }) {
  const [text, setText] = useState("");
  const inputRef = useRef(null);
  const boxRef = useRef(null);
  useEffect(() => { inputRef.current && inputRef.current.focus(); }, []);
  useEffect(() => {
    const h = (e) => { if (boxRef.current && !boxRef.current.contains(e.target)) onCancel(); };
    const k = (e) => { if (e.key === "Escape") { e.preventDefault(); onCancel(); } };
    document.addEventListener("mousedown", h);
    document.addEventListener("keydown", k, true);
    return () => { document.removeEventListener("mousedown", h); document.removeEventListener("keydown", k, true); };
  }, []);
  const left = Math.min(x, window.innerWidth - 360);
  const top = Math.min(y + 6, window.innerHeight - 150);
  const preview = (text.trim() ? text.trim() + " " : "") + refStr;
  return (
    <div className="pass-pop" ref={boxRef} style={{ left, top }}>
      <div className="pass-head">{Icon.spark({})}<span>Pass on to Agent</span><span className="pass-esc">esc</span></div>
      <input ref={inputRef} className="pass-input" value={text} spellCheck={false}
        placeholder="Add a note (optional)…"
        onChange={(e) => setText(e.target.value)}
        onKeyDown={(e) => {
          if (e.key === "Enter") { e.preventDefault(); onConfirm(text); }
          else if (e.key === "Escape") { e.preventDefault(); onCancel(); }
        }} />
      <div className="pass-preview"><span className="pp-lbl">inserts</span><code>{preview}</code></div>
      <div className="pass-foot"><kbd>↵</kbd> insert into agent · <kbd>esc</kbd> cancel</div>
    </div>
  );
}

Object.assign(window, { SearchModal, ContextMenu, Toasts, PassPopup, fuzzy });
