// cmdk.jsx — Ryoko · Universal ⌘K search
// Self-mounting. Include via:
//   <link rel="stylesheet" href="../shared/cmdk.css" />
//   <script type="text/babel" src="../shared/cmdk.jsx"></script>
// Open programmatically with window.RyokoCmdk.open();

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

  // ── Search index ────────────────────────────────────────────
  // In a real build this would be served per-member, scoped by
  // permissions. Here it's a curated demo set.
  const INDEX = [
    // Members
    { type: "member", glyph: "EA", label: "Mr. & Mrs. Aldercott",   sub: "No. 0418 · since March 2021 · London", href: "/profile" },
    // Architects
    { type: "architect", glyph: "CO", label: "Camille Okafor", sub: "Lead Architect · London · paired since 2021", href: "../landing/Landing.html" },
    { type: "architect", glyph: "TN", label: "Takumi Nakamura", sub: "Architect · Japan & East Asia · Kyoto annex", href: "../landing/Landing.html" },
    { type: "architect", glyph: "TS", label: "Themba Sithole", sub: "Architect · Africa & Indian Ocean · Johannesburg", href: "../landing/Landing.html" },
    { type: "architect", glyph: "HM", label: "Hélène Marchand", sub: "Architect · France · Switzerland · Monaco · Paris", href: "../landing/Landing.html" },
    { type: "architect", glyph: "YT", label: "Yuki Tanaka", sub: "Architect · Hokkaidō & winter mountain · Niseko", href: "../landing/Landing.html" },
    { type: "architect", glyph: "DV", label: "Diego Velasco", sub: "Architect · The Americas · NY", href: "../landing/Landing.html" },
    { type: "architect", glyph: "RD", label: "Rohan Desai", sub: "Architect · South Asia & Subcontinent · London", href: "../landing/Landing.html" },
    // Trips
    { type: "trip", glyph: "→", label: "Lisbon · the long weekend", sub: "12–18 Sept 2026 · on trip · Day 3 of 7 · Camille", href: "/trips" },
    { type: "trip", glyph: "→", label: "Kyoto · in maple season",   sub: "20 Nov–1 Dec 2026 · held · Rev 2 · awaiting Mrs A", href: "/trips" },
    { type: "trip", glyph: "→", label: "Mexico City + Oaxaca",       sub: "Easter 2027 · concept · proposal due Friday", href: "/trips" },
    { type: "trip", glyph: "→", label: "Aspen · winter family · same suite", sub: "8–13 March 2027 · confirmed · suite 4140", href: "/trips" },
    { type: "trip", glyph: "→", label: "Le Sirenuse · before the season turns", sub: "12–23 Oct 2026 · confirmed · 11 nights", href: "/trips" },
    // Venues (the vault, abbreviated)
    // Venue links — id matches the rich place slug where one exists,
    // so cmdk takes you to the full editorial page.  Where no slug
    // exists, the fallback uses label/sub/image to render a card-true
    // page (never the generic "This place" placeholder).
    { type: "venue", glyph: "★", id: "belcanto",          label: "Belcanto · Lisbon",         sub: "★★ · José Avillez · Largo de São Carlos", href: "../landing/Landing.html",    image: "https://images.unsplash.com/photo-1559339352-11d035aa65de?w=1400&q=80", eyebrow: "Lisbon · ★★ Michelin" },
    { type: "venue", glyph: "★", id: "pasteis-de-belem",  label: "Pastéis de Belém",          sub: "Pre-opening slot · 3 / week · Tiago",     href: "../landing/Landing.html",    image: "https://images.unsplash.com/photo-1551218808-94e220e084d2?w=1400&q=80", eyebrow: "Lisbon · pre-opening" },
    { type: "venue", glyph: "★", id: "pousada-de-lisboa", label: "Pousada de Lisboa",         sub: "Royal Suite · Pedro Castro GM",            href: "../landing/Landing.html",    image: "https://images.unsplash.com/photo-1571406761758-9a3eed5338ef?w=1400&q=80", eyebrow: "Lisbon · stay" },
    { type: "venue", glyph: "★", id: "pena-palace",       label: "Pena Palace · Sintra",      sub: "Out-of-hours · Joana Albuquerque",         href: "../landing/Landing.html",    image: "https://images.unsplash.com/photo-1545569341-9eb8b30979d9?w=1400&q=80", eyebrow: "Sintra · out-of-hours" },
    { type: "venue", glyph: "★", id: "quinta-da-regaleira", label: "Quinta da Regaleira",     sub: "Members' gate · Dr. Rita Pacheco",         href: "../landing/Landing.html",    image: "https://images.unsplash.com/photo-1493997181344-712f2f19d87a?w=1400&q=80", eyebrow: "Sintra · architect-led" },
    { type: "venue", glyph: "★", id: "hiiragiya-bekkan",  label: "Hiiragiya · Kyoto",         sub: "Open account · since 1818",                href: "../landing/Landing.html",    image: "https://images.unsplash.com/photo-1540541338287-41700207dee6?w=1400&q=80", eyebrow: "Kyoto · ryokan" },
    { type: "venue", glyph: "★", id: "kikunoi",           label: "Kikunoi honten · Murata-san", sub: "★★★ · Takumi · 6m lead",                  href: "../landing/Landing.html",    image: "https://images.unsplash.com/photo-1546069901-ba9599a7e63c?w=1400&q=80", eyebrow: "Kyoto · ★★★ kaiseki" },
    { type: "venue", glyph: "★", id: "aman-tokyo",        label: "Aman Tokyo",                sub: "Atelier rate card · suite priority",       href: "../landing/Landing.html",    image: "https://images.unsplash.com/photo-1578469645742-46cae010e5d4?w=1400&q=80", eyebrow: "Tokyo · Atelier rate" },
    { type: "venue", glyph: "★", id: "the-little-nell",   label: "The Little Nell · Aspen",   sub: "Suite 4140 · GM Klein · permanent Feb hold", href: "../landing/Landing.html",  image: "https://images.unsplash.com/photo-1551524559-8af4e6624178?w=1400&q=80", eyebrow: "Aspen · suite hold" },
    { type: "venue", glyph: "★", id: "chateau-la-coste",  label: "Château La Coste · Provence", sub: "Standing relationship · Lucas (GM)",     href: "../landing/Landing.html",    image: "https://images.unsplash.com/photo-1533104816931-20fa691ff6ca?w=1400&q=80", eyebrow: "Provence · standing" },
    { type: "venue", glyph: "★", id: "etxebarri",         label: "Etxebarri · Atxondo",       sub: "Bittor · 14 covers · Tuesdays held",       href: "../landing/Landing.html",    image: "https://images.unsplash.com/photo-1559339352-11d035aa65de?w=1400&q=80", eyebrow: "Basque · ★ Michelin" },
    { type: "venue", glyph: "★", id: "quinta-do-saloio",  label: "Quinta do Saloio · Setúbal", sub: "Vasco · architect-only · 14 covers",      href: "../landing/Landing.html",    image: "https://images.unsplash.com/photo-1543429776-2782fc8e1acd?w=1400&q=80", eyebrow: "Setúbal · architect-only" },
    { type: "venue", glyph: "★", id: "annabels",          label: "Annabel's · Mayfair",       sub: "8 LSE alumni in cohort are members",       href: "../landing/Landing.html",    image: "https://images.unsplash.com/photo-1414235077428-338989a2e8c0?w=1400&q=80", eyebrow: "Mayfair · members' club" },
    // Contacts (suppliers)
    { type: "contact", glyph: "PC", label: "Pedro Castro",   sub: "GM · Pousada de Lisboa · WhatsApp · before email", href: null },
    { type: "contact", glyph: "TS", label: "Tiago Soares",    sub: "Pastéis de Belém · pre-opening contact", href: null },
    { type: "contact", glyph: "JA", label: "Joana Albuquerque", sub: "Estate director · Pena Palace · Sintra", href: null },
    { type: "contact", glyph: "VL", label: "Vasco Lello",     sub: "Chef-patron · Quinta do Saloio · architect-only", href: null },
    { type: "contact", glyph: "JR", label: "João Rodrigues",  sub: "Chef · Feitoria · Lisbon · Project Matéria", href: null },
    { type: "contact", glyph: "YM", label: "Yoshihiro Murata", sub: "Chef-patron · Kikunoi honten · 15th gen · via Takumi", href: null },
    { type: "contact", glyph: "RP", label: "Dr. Rita Pacheco", sub: "Manini scholar · Quinta da Regaleira", href: null },
    // Notes (architect's running log on the Aldercott file)
    { type: "note", glyph: "✎", label: "Mr A asked about Bhutan in passing",  sub: "Camille · 28 April · 'don't surface until October'", href: "../atelier-portal/MemberFile.html" },
    { type: "note", glyph: "✎", label: "Mrs A's milestone anniversary draft", sub: "Camille · 12 April · Provence held quietly for March 2027", href: "../atelier-portal/MemberFile.html" },
    { type: "note", glyph: "✎", label: "Cap-Ferrat pacing note",              sub: "Camille · 22 March · 'too many three-Michelin tables in a row'", href: "../atelier-portal/MemberFile.html" },
    { type: "note", glyph: "✎", label: "EA Camilla now has draft-review rights", sub: "Camille · 18 Sept 2025 · Patagonia pacing intervention", href: "../atelier-portal/MemberFile.html" },
    // Pages / surfaces
    { type: "page", glyph: "◇", label: "The Studio · Camille's home",      sub: "Internal · Atelier portal", href: "../atelier-portal/Studio.html" },
    { type: "page", glyph: "◇", label: "Trip Composer",                     sub: "Internal · open the Lisbon file", href: "../atelier-portal/Composer.html" },
    { type: "page", glyph: "◇", label: "Member File · Aldercott",            sub: "Internal · the dossier", href: "../atelier-portal/MemberFile.html" },
    { type: "page", glyph: "◇", label: "Builder · my drafts",               sub: "Member · self-created itinerary", href: "../landing/Landing.html" },
    { type: "page", glyph: "◇", label: "Today · on-trip view",              sub: "Member · Lisbon, day-by-day", href: "../landing/Landing.html" },
    { type: "page", glyph: "◇", label: "Messages · with Camille",            sub: "Member · thread per trip", href: "../landing/Landing.html" },
    { type: "page", glyph: "◇", label: "Profile · what we know",             sub: "Member · enriched identity, audience controls", href: "/profile" },
    { type: "page", glyph: "◇", label: "Settings",                          sub: "Member · privacy, signals, notifications", href: "../profile/Settings.html" },
    { type: "page", glyph: "◇", label: "Onboarding · first-time member",    sub: "12-section intake", href: "../onboarding/Welcome.html" },
  ];

  const TYPE_LABELS = {
    all:       "All",
    member:    "Members",
    architect: "Architects",
    trip:      "Trips",
    venue:     "Venues",
    contact:   "Contacts",
    note:      "Notes",
    page:      "Pages",
  };
  const TYPE_ORDER = ["member", "architect", "trip", "venue", "contact", "note", "page"];

  const TIPS = [
    'Try "Camille", "Belcanto", "Bhutan", "Pedro", or "Aspen"',
    'Type "trip" to see all trips · "venue" for the vault',
    "↑ ↓ to navigate · Enter to open · Esc to close",
  ];

  function score(item, q) {
    if (!q) return 0;
    const haystack = `${item.label} ${item.sub}`.toLowerCase();
    const needle = q.toLowerCase();
    if (item.label.toLowerCase().startsWith(needle)) return 100;
    if (item.label.toLowerCase().includes(needle)) return 60;
    if (haystack.includes(needle)) return 30;
    return 0;
  }

  function Cmdk() {
    const [open, setOpen] = useState(false);
    const [q, setQ] = useState("");
    const [type, setType] = useState("all");
    const [cursor, setCursor] = useState(0);
    const inputRef = useRef(null);

    // Global keyboard shortcut
    useEffect(() => {
      const handler = (e) => {
        const isModK = (e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k";
        if (isModK) { e.preventDefault(); setOpen(o => !o); }
        if (e.key === "Escape" && open) setOpen(false);
      };
      window.addEventListener("keydown", handler);
      return () => window.removeEventListener("keydown", handler);
    }, [open]);

    // Expose programmatic API on the window
    useEffect(() => {
      window.RyokoCmdk = {
        open: () => setOpen(true),
        close: () => setOpen(false),
        toggle: () => setOpen(o => !o),
      };
    }, []);

    // Focus input when opening
    useEffect(() => {
      if (open && inputRef.current) {
        inputRef.current.focus();
        inputRef.current.select();
      }
    }, [open]);

    // Reset cursor when query/type changes
    useEffect(() => { setCursor(0); }, [q, type]);

    const results = useMemo(() => {
      const filtered = INDEX.filter(i => type === "all" || i.type === type);
      if (!q.trim()) {
        // No query: show first 24 of selected type, ordered by group
        return filtered.slice(0, 24);
      }
      return filtered
        .map(i => ({ i, s: score(i, q) }))
        .filter(x => x.s > 0)
        .sort((a, b) => b.s - a.s)
        .slice(0, 24)
        .map(x => x.i);
    }, [q, type]);

    // Group results by type for display when no query
    const grouped = useMemo(() => {
      if (q.trim()) return null; // flat list when searching
      const map = {};
      results.forEach(r => { (map[r.type] = map[r.type] || []).push(r); });
      return map;
    }, [results, q]);

    // Counts per type for the chip row
    const counts = useMemo(() => {
      const m = { all: INDEX.length };
      INDEX.forEach(i => { m[i.type] = (m[i.type] || 0) + 1; });
      return m;
    }, []);

    // Keyboard nav
    const onKey = (e) => {
      if (e.key === "ArrowDown") {
        e.preventDefault(); setCursor(c => Math.min(c + 1, results.length - 1));
      } else if (e.key === "ArrowUp") {
        e.preventDefault(); setCursor(c => Math.max(c - 1, 0));
      } else if (e.key === "Enter") {
        e.preventDefault();
        const r = results[cursor];
        if (r && r.href) { window.location.href = r.href; setOpen(false); }
      } else if (e.key === "Tab") {
        e.preventDefault();
        const idx = ["all", ...TYPE_ORDER].indexOf(type);
        const next = ["all", ...TYPE_ORDER][(idx + 1) % (TYPE_ORDER.length + 1)];
        setType(next);
      }
    };

    if (!open) return null;

    const onResultClick = (r) => {
      if (r.href) { window.location.href = r.href; setOpen(false); }
    };

    return (
      <div className="ryk-cmdk-veil" onClick={(e) => e.target === e.currentTarget && setOpen(false)}>
        <div className="ryk-cmdk" data-screen-label="Cmd+K search">
          <div className="ryk-cmdk-input-wrap">
            <span className="ryk-cmdk-icon">⌕</span>
            <input
              ref={inputRef}
              className="ryk-cmdk-input"
              placeholder="Search members · trips · venues · contacts · notes…"
              value={q}
              onChange={(e) => setQ(e.target.value)}
              onKeyDown={onKey}
            />
            <span className="ryk-cmdk-esc">Esc</span>
          </div>

          <div className="ryk-cmdk-types">
            {["all", ...TYPE_ORDER].map(t => (
              <button key={t}
                      className={`ryk-cmdk-type ${type === t ? "active" : ""}`}
                      onClick={() => setType(t)}>
                {TYPE_LABELS[t]}
                <span className="ryk-cmdk-type-count">{counts[t] || 0}</span>
              </button>
            ))}
          </div>

          <div className="ryk-cmdk-results">
            {results.length === 0 ? (
              <div className="ryk-cmdk-empty">
                Nothing matched.
                <span>{TIPS[0]}</span>
              </div>
            ) : grouped ? (
              // Grouped (no query)
              TYPE_ORDER.map(t => {
                const arr = grouped[t];
                if (!arr || arr.length === 0) return null;
                return (
                  <React.Fragment key={t}>
                    <div className="ryk-cmdk-section-head">{TYPE_LABELS[t]} · {arr.length}</div>
                    {arr.map((r, i) => {
                      const flatIdx = results.indexOf(r);
                      return (
                        <a key={`${t}-${i}`}
                           className={`ryk-cmdk-result ${cursor === flatIdx ? "cursor" : ""}`}
                           href={(window.RkLinks && r.href) ? window.RkLinks.enrichSubject({ ...r, title: r.label }) : (r.href || "#")}
                           onClick={(e) => { if (!r.href) e.preventDefault(); onResultClick(r); }}>
                          <span className="ryk-cmdk-result-glyph">{r.glyph}</span>
                          <div className="ryk-cmdk-result-body">
                            <div className="ryk-cmdk-result-label">{r.label}</div>
                            <div className="ryk-cmdk-result-sub">{r.sub}</div>
                          </div>
                          <span className="ryk-cmdk-result-type">{r.type}</span>
                        </a>
                      );
                    })}
                  </React.Fragment>
                );
              })
            ) : (
              // Flat (with query)
              results.map((r, i) => (
                <a key={i}
                   className={`ryk-cmdk-result ${cursor === i ? "cursor" : ""}`}
                   href={r.href || "#"}
                   onClick={(e) => { if (!r.href) e.preventDefault(); onResultClick(r); }}>
                  <span className="ryk-cmdk-result-glyph">{r.glyph}</span>
                  <div className="ryk-cmdk-result-body">
                    <div className="ryk-cmdk-result-label">{r.label}</div>
                    <div className="ryk-cmdk-result-sub">{r.sub}</div>
                  </div>
                  <span className="ryk-cmdk-result-type">{r.type}</span>
                </a>
              ))
            )}
          </div>

          {!q.trim() && results.length > 0 && (
            <div className="ryk-cmdk-hint">
              <b>Tip ·</b> {TIPS[Math.floor(Math.random() * TIPS.length)]}
            </div>
          )}

          <div className="ryk-cmdk-foot">
            <div className="ryk-cmdk-foot-l">
              <span><kbd>↑</kbd><kbd>↓</kbd> navigate</span>
              <span><kbd>↵</kbd> open</span>
              <span><kbd>Tab</kbd> switch type</span>
            </div>
            <div className="ryk-cmdk-foot-r">
              <span>Universal search · ⌘K anywhere</span>
            </div>
          </div>
        </div>
      </div>
    );
  }

  // Self-mount
  function mount() {
    if (document.getElementById("ryoko-cmdk-root")) return;
    const div = document.createElement("div");
    div.id = "ryoko-cmdk-root";
    document.body.appendChild(div);
    ReactDOM.createRoot(div).render(<Cmdk />);
  }
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", mount);
  } else {
    mount();
  }
})();
