/* ============================================================================
   CommandPalette — ⌘K global search over writing, projects, pages, actions.
   Keyboard-first, scoped, with recents + monochrome match highlighting.
   Depends on globals from components.jsx: Icon, POSTS, PROJECTS, Crosshair.
   ========================================================================== */

const CMDK_RECENT_KEY = "amh-cmdk-recent";

/* --- recents (localStorage) ------------------------------------------------ */
function loadRecents() {
  try { return JSON.parse(localStorage.getItem(CMDK_RECENT_KEY) || "[]"); }
  catch (e) { return []; }
}
function saveRecents(list) {
  try { localStorage.setItem(CMDK_RECENT_KEY, JSON.stringify(list.slice(0, 6))); } catch (e) {}
}

/* --- text helpers ---------------------------------------------------------- */
function escRe(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); }

function Highlight({ text, query }) {
  const tokens = (query || "").trim().split(/\s+/).filter(Boolean).map(escRe);
  if (!tokens.length) return text;
  const re = new RegExp("(" + tokens.join("|") + ")", "ig");
  const parts = String(text).split(re);
  return parts.map((p, i) => (i % 2 === 1 ? <mark key={i}>{p}</mark> : p));
}

/* --- scoring: AND over tokens; title prefix/contains weighted highest ------ */
function scoreItem(item, q) {
  const tokens = q.split(/\s+/).filter(Boolean);
  if (!tokens.length) return 0;
  const title = item.title.toLowerCase();
  const hay = item._hay;
  let s = 0;
  for (const t of tokens) {
    if (hay.indexOf(t) === -1) return -1; // every token must appear somewhere
    const it = title.indexOf(t);
    if (it === 0) s += 100;
    else if (it > 0) s += 60 - Math.min(it, 40);
    else s += 18;
  }
  if (title.includes(q)) s += 40;
  return s;
}

const SCOPES = [
  { key: "all", label: "All" },
  { key: "writing", label: "Writing" },
  { key: "projects", label: "Projects" },
  { key: "pages", label: "Pages" },
  { key: "actions", label: "Actions" },
];
const TYPE_ORDER = ["writing", "projects", "pages", "actions"];
const TYPE_LABEL = { writing: "Writing", projects: "Projects", pages: "Pages", actions: "Actions" };

function CommandPalette({ open, onClose, go, openPost, goWriting, theme, toggleTheme }) {
  const [query, setQuery] = React.useState("");
  const [scope, setScope] = React.useState("all");
  const [active, setActive] = React.useState(0);
  const [recents, setRecents] = React.useState(loadRecents);
  const [toast, setToast] = React.useState(null);
  const inputRef = React.useRef(null);
  const listRef = React.useRef(null);
  const rowRefs = React.useRef([]);
  const toastTimer = React.useRef(null);

  const flash = (msg) => {
    setToast(msg);
    clearTimeout(toastTimer.current);
    toastTimer.current = setTimeout(() => setToast(null), 1500);
  };

  /* ---- build the searchable index (rebuilds when actions' context changes) -- */
  const index = React.useMemo(() => {
    const items = [];

    POSTS.forEach((p) => items.push({
      id: "w-" + p.id, type: "writing", icon: "file-text",
      title: p.title,
      sub: `${p.date} · ${p.read} · ${p.tags.map((t) => "#" + t).join(" ")}`,
      kicker: p.cat,
      keywords: `${p.desc} ${p.cat} ${p.tags.join(" ")}`,
      run: () => openPost(p),
    }));

    PROJECTS.forEach((p) => items.push({
      id: "j-" + p.id, type: "projects", icon: "box",
      title: p.title,
      sub: `${p.lang} · ★ ${p.stars} · ${p.year}`,
      kicker: "Project",
      keywords: `${p.tagline} ${p.lang} open source`,
      run: () => go("projects"),
    }));

    const PAGES = [
      { id: "home", title: "Home", sub: "The front door", icon: "home", kw: "landing front start" },
      { id: "writing", title: "Writing", sub: "Essays & notes", icon: "pen-line", kw: "blog articles essays notes archive" },
      { id: "projects", title: "Projects", sub: "Things I've built", icon: "box", kw: "portfolio work shipped tools" },
      { id: "photos", title: "Photos", sub: "Seen, kept", icon: "image", kw: "gallery photography pictures" },
      { id: "about", title: "About", sub: "Who & why", icon: "user", kw: "bio me aaron hampton contact" },
      { id: "subscribe", title: "Newsletter", sub: "Subscribe — leave equipped", icon: "mail", kw: "subscribe email join updates" },
    ];
    PAGES.forEach((pg) => items.push({
      id: "pg-" + pg.id, type: "pages", icon: pg.icon,
      title: pg.title, sub: pg.sub, kicker: "Page", keywords: pg.kw,
      run: () => go(pg.id),
    }));

    // quick actions
    items.push({
      id: "a-theme", type: "actions", icon: theme === "dark" ? "sun" : "moon",
      title: `Switch to ${theme === "dark" ? "light" : "dark"} theme`,
      sub: "Toggle the page appearance", kicker: "Toggle",
      keywords: "theme dark light mode appearance switch",
      keepOpen: true, run: () => toggleTheme(),
    });
    items.push({
      id: "a-subscribe", type: "actions", icon: "mail",
      title: "Subscribe to the newsletter", sub: "One idea, worked down to a next action",
      kicker: "Action", keywords: "subscribe newsletter email join signup",
      run: () => go("subscribe"),
    });
    items.push({
      id: "a-copy", type: "actions", icon: "link",
      title: "Copy link to this page", sub: "Put the current URL on your clipboard",
      kicker: "Action", keywords: "copy link share url permalink",
      keepOpen: true,
      run: () => {
        const url = window.location.href;
        if (navigator.clipboard && navigator.clipboard.writeText) {
          navigator.clipboard.writeText(url).then(() => flash("↳ Link copied")).catch(() => flash("↳ Copy failed"));
        } else { flash("↳ " + url); }
      },
    });
    items.push({
      id: "a-rss", type: "actions", icon: "rss",
      title: "Subscribe via RSS", sub: "Plain feed — read it anywhere",
      kicker: "Action", keywords: "rss feed atom syndication",
      run: () => { window.open("#", "_blank"); },
    });
    items.push({
      id: "a-github", type: "actions", icon: "github",
      title: "View source on GitHub", sub: "The engine behind this site",
      kicker: "Action", keywords: "github source code repository open",
      run: () => { window.open("#", "_blank"); },
    });
    ["philosophy", "technology", "history", "politics", "science"].forEach((tag) => items.push({
      id: "a-tag-" + tag, type: "actions", icon: "hash",
      title: `Browse #${tag} writing`, sub: "Filter the writing index by this tag",
      kicker: "Filter", keywords: `tag filter topic ${tag} writing`,
      run: () => goWriting(tag),
    }));

    items.forEach((it) => { it._hay = (it.title + " " + (it.keywords || "")).toLowerCase(); });
    return items;
  }, [go, openPost, goWriting, theme, toggleTheme]);

  /* ---- per-scope counts for the chips (respect current query) --------------- */
  const counts = React.useMemo(() => {
    const q = query.trim().toLowerCase();
    const c = { all: 0, writing: 0, projects: 0, pages: 0, actions: 0 };
    index.forEach((it) => {
      if (q && scoreItem(it, q) < 0) return;
      c.all++; c[it.type]++;
    });
    return c;
  }, [index, query]);

  /* ---- grouped, ordered results --------------------------------------------- */
  const groups = React.useMemo(() => {
    const q = query.trim().toLowerCase();
    let pool = index;
    if (scope !== "all") pool = pool.filter((it) => it.type === scope);

    if (!q) {
      // empty-query "browse" state
      if (scope === "all") {
        const g = [];
        const pages = index.filter((it) => it.type === "pages");
        const posts = index.filter((it) => it.type === "writing").slice(0, 4);
        const acts = index.filter((it) => it.type === "actions").slice(0, 4);
        g.push({ key: "pages", label: "Jump to", items: pages });
        g.push({ key: "writing", label: "Suggested reading", items: posts });
        g.push({ key: "actions", label: "Quick actions", items: acts });
        return g;
      }
      return [{ key: scope, label: TYPE_LABEL[scope], items: pool }];
    }

    const scored = pool
      .map((it) => ({ it, s: scoreItem(it, q) }))
      .filter((x) => x.s >= 0)
      .sort((a, b) => b.s - a.s);

    const byType = {};
    scored.forEach(({ it }) => { (byType[it.type] = byType[it.type] || []).push(it); });
    return TYPE_ORDER
      .filter((t) => byType[t] && byType[t].length)
      .map((t) => ({ key: t, label: TYPE_LABEL[t], items: byType[t] }));
  }, [index, query, scope]);

  const flat = React.useMemo(() => groups.flatMap((g) => g.items), [groups]);

  /* ---- reset on open + focus ------------------------------------------------ */
  React.useEffect(() => {
    if (open) {
      setQuery(""); setScope("all"); setActive(0); setRecents(loadRecents());
      const t = setTimeout(() => inputRef.current && inputRef.current.focus(), 30);
      document.body.style.overflow = "hidden";
      return () => { clearTimeout(t); document.body.style.overflow = ""; };
    }
  }, [open]);

  React.useEffect(() => { setActive(0); }, [query, scope]);

  /* ---- keep active row visible (no scrollIntoView) -------------------------- */
  React.useEffect(() => {
    const el = rowRefs.current[active];
    const list = listRef.current;
    if (!el || !list) return;
    const top = el.offsetTop;
    const bottom = top + el.offsetHeight;
    if (top < list.scrollTop) list.scrollTop = top - 8;
    else if (bottom > list.scrollTop + list.clientHeight) list.scrollTop = bottom - list.clientHeight + 8;
  }, [active]);

  if (!open) return null;

  const addRecent = (q) => {
    const v = q.trim();
    if (v.length < 2) return;
    const next = [v, ...recents.filter((r) => r.toLowerCase() !== v.toLowerCase())].slice(0, 6);
    setRecents(next); saveRecents(next);
  };

  const activate = (item) => {
    if (!item) return;
    if (query.trim().length >= 2) addRecent(query);
    item.run();
    if (!item.keepOpen) onClose();
  };

  const onKeyDown = (e) => {
    if (e.key === "ArrowDown") { e.preventDefault(); setActive((i) => (flat.length ? (i + 1) % flat.length : 0)); }
    else if (e.key === "ArrowUp") { e.preventDefault(); setActive((i) => (flat.length ? (i - 1 + flat.length) % flat.length : 0)); }
    else if (e.key === "Enter") { e.preventDefault(); activate(flat[active]); }
    else if (e.key === "Escape") { e.preventDefault(); onClose(); }
    else if (e.key === "Tab") {
      e.preventDefault();
      const dir = e.shiftKey ? -1 : 1;
      const i = SCOPES.findIndex((s) => s.key === scope);
      setScope(SCOPES[(i + dir + SCOPES.length) % SCOPES.length].key);
    }
  };

  const mod = (typeof navigator !== "undefined" && /Mac|iPhone|iPad/.test(navigator.platform || "")) ? "⌘" : "Ctrl";

  // running flat index across groups for active mapping
  let counter = -1;

  return (
    <div className="cmdk-overlay" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div className="cmdk" role="dialog" aria-modal="true" aria-label="Search">
        <div className="cmdk-reg"><Crosshair /></div>

        <div className="cmdk-field">
          <span className="cmdk-search-ic"><Icon name="search" size={18} /></span>
          <input
            ref={inputRef}
            className="cmdk-input"
            placeholder="Search writing, projects, actions…"
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            onKeyDown={onKeyDown}
            spellCheck={false}
            autoComplete="off"
          />
        </div>

        <div className="cmdk-scopes">
          {SCOPES.map((s) => (
            <button key={s.key} className={"cmdk-scope" + (scope === s.key ? " on" : "")}
              onClick={() => setScope(s.key)}>
              {s.label}<span className="ct">{counts[s.key]}</span>
            </button>
          ))}
        </div>

        <div className="cmdk-list" ref={listRef}>
          {!query && recents.length > 0 && (
            <React.Fragment>
              <div className="cmdk-group-label">Recent searches</div>
              <div className="cmdk-recents">
                {recents.map((r) => (
                  <button key={r} className="cmdk-recent" onClick={() => { setQuery(r); inputRef.current && inputRef.current.focus(); }}>
                    <span className="rx">↳</span>{r}
                  </button>
                ))}
              </div>
            </React.Fragment>
          )}

          {flat.length === 0 ? (
            <div className="cmdk-empty">
              <span className="ce-mark"><Crosshair /></span>
              <p className="ce-t">No matches for “{query}”</p>
              <p className="ce-d">Try a topic (#philosophy), a project, or an action like “theme”.</p>
            </div>
          ) : (
            groups.map((g) => (
              <div key={g.key} className="cmdk-section">
                <div className="cmdk-group-label">
                  <span>{g.label}</span><span className="gct">{g.items.length}</span>
                </div>
                {g.items.map((item) => {
                  counter += 1;
                  const idx = counter;
                  const isActive = idx === active;
                  return (
                    <div
                      key={item.id}
                      ref={(el) => { rowRefs.current[idx] = el; }}
                      className={"cmdk-row" + (isActive ? " active" : "")}
                      onMouseEnter={() => setActive(idx)}
                      onClick={() => activate(item)}
                    >
                      <span className="cmdk-ic"><Icon name={item.icon} size={17} /></span>
                      <span className="cmdk-main">
                        <span className="cmdk-title"><Highlight text={item.title} query={query} /></span>
                        <span className="cmdk-sub"><Highlight text={item.sub} query={query} /></span>
                      </span>
                      {isActive
                        ? <span className="cmdk-enter">Open&nbsp;↵</span>
                        : <span className="cmdk-kicker">{item.kicker}</span>}
                    </div>
                  );
                })}
              </div>
            ))
          )}
        </div>

        <div className="cmdk-foot">
          <div className="cmdk-hints">
            <span className="cmdk-hint"><span className="kc">↑</span><span className="kc">↓</span> navigate</span>
            <span className="cmdk-hint"><span className="kc">↵</span> open</span>
            <span className="cmdk-hint"><span className="kc">tab</span> scope</span>
            <span className="cmdk-hint"><span className="kc">esc</span> close</span>
          </div>
          <span className="cmdk-count">{flat.length} result{flat.length === 1 ? "" : "s"}</span>
        </div>

        {toast && <div className="cmdk-toast">{toast}</div>}
      </div>
    </div>
  );
}

Object.assign(window, { CommandPalette });
