// Leader callout & feedback widget for w.minimaxmart.com
// Auth-gated: only renders for logged-in Nexus users (same-origin session cookie)
// Mounts independently of the main app — zero changes to existing site files
(function(){
  const { useState, useEffect, useRef, useCallback, useMemo } = React;
  const API = "/api/website-feedback";
  const STORAGE_KEY = "mm_fb_open";

  // ---- Bootstrap ----
  console.log("[mm-fb] script loaded, checking auth...");
  fetch("/api/auth/session", { credentials: "include" })
    .then(function(r){ console.log("[mm-fb] auth response status:", r.status); return r.ok ? r.json() : null; })
    .then(function(user){
      if (user && user.id) {
        console.log("[mm-fb] authed as", user.display_name, "— mounting widget");
        bootstrap(user);
      } else {
        console.log("[mm-fb] not authed — widget hidden (expected for public visitors)");
      }
    })
    .catch(function(e){ console.error("[mm-fb] auth check failed:", e); });

  function bootstrap(currentUser) {
    injectStyles();
    let host = document.getElementById("mm-fb-root");
    if (!host) {
      host = document.createElement("div");
      host.id = "mm-fb-root";
      document.body.appendChild(host);
    }
    ReactDOM.createRoot(host).render(<FeedbackWidget currentUser={currentUser} />);
  }

  function injectStyles() {
    if (document.getElementById("mm-fb-styles")) return;
    const s = document.createElement("style");
    s.id = "mm-fb-styles";
    s.textContent = STYLES;
    document.head.appendChild(s);
  }

  // ---- Helpers ----
  const getRoute = () => (window.location.hash || "#home").replace("#", "") || "home";

  function getAnchorText(el) {
    let cur = el;
    while (cur && cur !== document.body) {
      const tag = cur.tagName ? cur.tagName.toLowerCase() : "";
      if (["h1","h2","h3","h4"].indexOf(tag) >= 0) {
        return (cur.textContent || "").trim().slice(0, 80);
      }
      cur = cur.parentElement;
    }
    const headings = Array.from(document.querySelectorAll("h1,h2,h3,h4"));
    let nearest = null, nearestDist = Infinity;
    const elTop = el.getBoundingClientRect().top + window.scrollY;
    for (const h of headings) {
      const hTop = h.getBoundingClientRect().top + window.scrollY;
      if (hTop <= elTop && (elTop - hTop) < nearestDist) { nearestDist = elTop - hTop; nearest = h; }
    }
    return nearest ? (nearest.textContent || "").trim().slice(0, 80) : "";
  }

  function renderMessageWithMentions(msg) {
    const re = /@(?:"([^"]+)"|([A-Za-z][\w.-]*))/g;
    const out = []; let last = 0, m, i = 0;
    while ((m = re.exec(msg)) !== null) {
      if (m.index > last) out.push(msg.slice(last, m.index));
      const name = m[1] || m[2];
      out.push(<span key={"m"+(i++)} className="mm-fb-mention">@{name}</span>);
      last = m.index + m[0].length;
    }
    if (last < msg.length) out.push(msg.slice(last));
    return out;
  }

  function isDirectedAt(msg, displayName) {
    if (!msg || !displayName) return false;
    const re = /@(?:"([^"]+)"|([A-Za-z][\w.-]*))/g;
    let m;
    while ((m = re.exec(msg)) !== null) {
      const name = (m[1] || m[2] || "").toLowerCase();
      if (name === displayName.toLowerCase()) return true;
    }
    return false;
  }

  function fmtTime(ts) {
    return new Date(ts).toLocaleString("en-US", { month:"short", day:"numeric", hour:"2-digit", minute:"2-digit" });
  }

  function locatePin(pinId) {
    const el = document.querySelector('[data-mm-fb-pin-id="' + pinId + '"]');
    if (!el) return false;
    const y = parseFloat(el.style.top) || 0;
    window.scrollTo({ top: Math.max(0, y - window.innerHeight / 2), behavior: "smooth" });
    el.classList.remove("flash");
    void el.offsetWidth;
    el.classList.add("flash");
    setTimeout(function(){ el && el.classList.remove("flash"); }, 3500);
    return true;
  }

  function buildNumberMap(items, replies) {
    const numMap = {};
    let p = 0, pn = 0, sn = 0;
    for (const it of items) {
      if (it.kind === "pin") { p++; numMap[it.id] = "P" + p; }
      else if (it.kind === "note_page") { pn++; numMap[it.id] = "PN" + pn; }
      else if (it.kind === "note_site") { sn++; numMap[it.id] = "SN" + sn; }
    }
    function assignReplies(parentId, parentNum) {
      const kids = replies.filter(function(r){ return r.parent_id === parentId; });
      kids.forEach(function(r, i){
        const num = parentNum + "." + (i + 1);
        numMap[r.id] = num;
        assignReplies(r.id, num);
      });
    }
    for (const it of items) assignReplies(it.id, numMap[it.id]);
    return numMap;
  }

  // ---- Styles ----
  const STYLES = [
    "#mm-fb-root, #mm-fb-root * { box-sizing: border-box; }",
    ".mm-fb-fab { position: fixed; right: 24px; bottom: 24px; z-index: 9000; background: var(--mm-red,#E2231A); color: #fff; border: none; border-radius: 999px; padding: 12px 20px; font-family: var(--font-display,'Bebas Neue'),Impact,sans-serif; font-size: 14px; letter-spacing: 0.06em; text-transform: uppercase; cursor: pointer; box-shadow: 0 8px 24px -4px rgba(226,35,26,0.4); display: flex; align-items: center; gap: 8px; transition: transform 0.15s; }",
    ".mm-fb-fab:hover { transform: translateY(-2px); }",
    ".mm-fb-fab .badge { background: rgba(255,255,255,0.25); padding: 2px 8px; border-radius: 999px; font-size: 12px; }",
    ".mm-fb-panel { position: fixed; right: 24px; bottom: 80px; z-index: 9001; width: 380px; max-height: 70vh; background: var(--bg,#fff); color: var(--text,#0E0E0E); border: 1px solid var(--border,#E5E5E0); border-radius: 14px; box-shadow: 0 24px 48px -12px rgba(0,0,0,0.3); display: flex; flex-direction: column; overflow: hidden; font-family: var(--font-sans,'Inter Tight'),system-ui,sans-serif; font-size: 14px; }",
    ".mm-fb-header { display: flex; align-items: center; justify-content: space-between; padding: 12px 16px; border-bottom: 1px solid var(--border,#E5E5E0); }",
    ".mm-fb-header h3 { margin: 0; font-size: 13px; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; }",
    ".mm-fb-header .close { background: transparent; border: none; cursor: pointer; color: var(--text-3,#8A8A86); font-size: 22px; line-height: 1; padding: 0 4px; }",
    ".mm-fb-tabs { display: flex; border-bottom: 1px solid var(--border,#E5E5E0); }",
    ".mm-fb-tabs button { flex: 1; padding: 10px 12px; background: transparent; border: none; cursor: pointer; font-family: var(--font-mono,'JetBrains Mono'),monospace; font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: var(--text-3,#8A8A86); border-bottom: 2px solid transparent; }",
    ".mm-fb-tabs button.active { color: var(--mm-red,#E2231A); border-bottom-color: var(--mm-red,#E2231A); }",
    ".mm-fb-body { flex: 1; overflow-y: auto; padding: 12px; }",
    ".mm-fb-actions { display: flex; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; align-items: center; }",
    ".mm-fb-btn { padding: 7px 14px; border-radius: 999px; border: 1px solid var(--border,#E5E5E0); background: var(--surface,#FAFAF8); color: var(--text,#0E0E0E); font-size: 12px; font-weight: 500; cursor: pointer; font-family: inherit; }",
    ".mm-fb-btn:hover { border-color: var(--border-strong,#C8C8C2); }",
    ".mm-fb-btn.primary { background: var(--mm-red,#E2231A); color: #fff; border-color: var(--mm-red,#E2231A); }",
    ".mm-fb-btn.ghost { background: transparent; }",
    ".mm-fb-btn:disabled { opacity: 0.5; cursor: not-allowed; }"
  ].join("\n") + "\n" + [
    ".mm-fb-item { padding: 10px 12px; border: 1px solid var(--border,#E5E5E0); border-radius: 8px; margin-bottom: 8px; background: var(--surface,#FAFAF8); cursor: pointer; }",
    ".mm-fb-item:hover { border-color: var(--mm-red,#E2231A); }",
    ".mm-fb-item.resolved { opacity: 0.55; }",
    ".mm-fb-item .meta { display: flex; gap: 8px; align-items: baseline; font-size: 11px; color: var(--text-3,#8A8A86); margin-bottom: 4px; font-family: var(--font-mono,'JetBrains Mono'),monospace; flex-wrap: wrap; }",
    ".mm-fb-item .author { font-weight: 600; color: var(--text-2,#4A4A48); }",
    ".mm-fb-item .badge { padding: 1px 6px; border-radius: 4px; background: var(--mm-red,#E2231A); color: #fff; font-size: 10px; }",
    ".mm-fb-item .badge.note { background: var(--text-3,#8A8A86); }",
    ".mm-fb-item .msg { font-size: 13px; line-height: 1.4; white-space: pre-wrap; word-break: break-word; }",
    ".mm-fb-item .anchor { font-size: 11px; color: var(--text-3,#8A8A86); margin-top: 4px; font-style: italic; }",
    ".mm-fb-empty { text-align: center; padding: 24px 12px; color: var(--text-3,#8A8A86); font-size: 13px; }",
    ".mm-fb-mention-list { position: absolute; bottom: 100%; left: 12px; right: 12px; background: var(--bg,#fff); border: 1px solid var(--border,#E5E5E0); border-radius: 8px; box-shadow: 0 8px 16px -4px rgba(0,0,0,0.15); max-height: 200px; overflow-y: auto; z-index: 9100; }",
    ".mm-fb-mention-list button { width: 100%; text-align: left; padding: 8px 12px; background: transparent; border: none; cursor: pointer; color: var(--text,#0E0E0E); font-size: 13px; font-family: inherit; }",
    ".mm-fb-mention-list button:hover, .mm-fb-mention-list button.active { background: var(--surface-2,#F0F0EC); }",
    ".mm-fb-pin { position: absolute; width: 28px; height: 28px; border-radius: 50%; background: var(--mm-red,#E2231A); color: #fff; display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; cursor: pointer; z-index: 8500; box-shadow: 0 2px 8px rgba(0,0,0,0.3); transform: translate(-50%, -50%); border: 2px solid #fff; transition: transform 0.15s; font-family: inherit; }",
    ".mm-fb-pin:hover { transform: translate(-50%, -50%) scale(1.15); }",
    ".mm-fb-pin.resolved { background: var(--text-3,#8A8A86); }",
    ".mm-fb-pin.directed { box-shadow: 0 0 0 3px rgba(226,35,26,0.3), 0 2px 8px rgba(0,0,0,0.3); }"
,
    ".mm-fb-drop-overlay { position: fixed; inset: 0; z-index: 8900; cursor: crosshair; background: rgba(226,35,26,0.04); }",
    ".mm-fb-drop-banner { position: fixed; top: 24px; left: 50%; transform: translateX(-50%); background: var(--mm-red,#E2231A); color: #fff; padding: 10px 20px; border-radius: 999px; font-size: 13px; font-family: var(--font-mono,'JetBrains Mono'),monospace; letter-spacing: 0.05em; z-index: 8901; pointer-events: none; }",
    ".mm-fb-mention { color: var(--mm-red,#E2231A); font-weight: 600; }",
    ".mm-fb-item-actions { display: flex; gap: 6px; margin-top: 6px; }",
    ".mm-fb-item-actions button { font-size: 11px; padding: 2px 8px; background: transparent; border: 1px solid var(--border,#E5E5E0); border-radius: 4px; color: var(--text-2,#4A4A48); cursor: pointer; font-family: inherit; }",
    ".mm-fb-item-actions button:hover { color: var(--text,#0E0E0E); border-color: var(--border-strong,#C8C8C2); }",
    ".mm-fb-item-actions button.danger:hover { color: var(--mm-red,#E2231A); border-color: var(--mm-red,#E2231A); }",
    ".mm-fb-section-label { font-size: 10px; color: var(--text-3,#8A8A86); text-transform: uppercase; letter-spacing: 0.12em; margin: 12px 0 4px; font-family: var(--font-mono,'JetBrains Mono'),monospace; }",
    ".mm-fb-reply { padding: 6px 10px; margin-top: 6px; margin-left: 12px; border-left: 2px solid var(--border,#E5E5E0); border-radius: 4px; }",
    ".mm-fb-reply.directed { border-left-color: var(--mm-red,#E2231A); background: rgba(226,35,26,0.04); }",
    ".mm-fb-back { background: transparent; border: none; cursor: pointer; color: var(--mm-red,#E2231A); font-size: 12px; padding: 0; margin-bottom: 8px; text-align: left; font-family: inherit; }",
    ".mm-fb-pin.flash { animation: mm-fb-pulse 0.5s ease-in-out 6 alternate; }",
    "@keyframes mm-fb-pulse { 0% { transform: translate(-50%,-50%) scale(1); box-shadow: 0 0 0 0 rgba(226,35,26,0.7), 0 2px 8px rgba(0,0,0,0.3); } 100% { transform: translate(-50%,-50%) scale(1.4); box-shadow: 0 0 0 16px rgba(226,35,26,0), 0 2px 8px rgba(0,0,0,0.3); } }",
    ".mm-fb-locate { font-size: 11px; padding: 3px 10px; background: var(--mm-red,#E2231A); color: #fff; border: none; border-radius: 4px; cursor: pointer; font-family: var(--font-mono,'JetBrains Mono'),monospace; letter-spacing: 0.05em; }",
    ".mm-fb-locate:hover { filter: brightness(1.1); }",
    ".mm-fb-id { font-family: var(--font-mono,'JetBrains Mono'),monospace; font-weight: 700; color: var(--mm-red,#E2231A); letter-spacing: 0.05em; }",
    ".mm-fb-edit-form textarea { width:100%; min-height: 60px; resize: vertical; padding: 6px 8px; border: 1px solid var(--border-strong,#C8C8C2); border-radius: 6px; background: var(--bg,#fff); color: var(--text,#0E0E0E); font-family: inherit; font-size: 13px; line-height: 1.4; margin-top: 4px; }",
    ".mm-fb-edit-form { margin-top: 4px; }",
    ".mm-fb-edit-form .actions { display: flex; gap: 6px; margin-top: 6px; justify-content: flex-end; }",
    "@media (max-width: 640px) {",
    "  .mm-fb-panel { right: 0; left: 0; bottom: 0; width: auto; max-height: 85vh; border-radius: 16px 16px 0 0; }",
    "  .mm-fb-fab { right: 12px; bottom: 12px; padding: 12px 16px; }",
    "  .mm-fb-fab .label { display: none; }",
    "  .mm-fb-drop-banner { font-size: 11px; padding: 8px 14px; }",
    "  .mm-fb-pin { width: 32px; height: 32px; font-size: 13px; }",
    "  .mm-fb-mention-list { max-height: 160px; }",
    "  .mm-fb-back { padding: 8px 4px; font-size: 14px; }",
    "}"
  ].join("\n");

  // ---- Components ----
  function FeedbackWidget({ currentUser }) {
    const [open, setOpen] = useState(() => localStorage.getItem(STORAGE_KEY) === "1");
    const [tab, setTab] = useState("pins");
    const [items, setItems] = useState([]);
    const [replies, setReplies] = useState([]);
    const [users, setUsers] = useState([]);
    const [route, setRoute] = useState(getRoute);
    const [dropMode, setDropMode] = useState(false);
    const [selectedThread, setSelectedThread] = useState(null);
    const [composeNoteScope, setComposeNoteScope] = useState(null);
    const [pendingPin, setPendingPin] = useState(null);

    useEffect(function(){ localStorage.setItem(STORAGE_KEY, open ? "1" : "0"); }, [open]);

    useEffect(function(){
      const onHash = function(){ setRoute(getRoute()); setSelectedThread(null); };
      window.addEventListener("hashchange", onHash);
      return function(){ window.removeEventListener("hashchange", onHash); };
    }, []);

    const refresh = useCallback(function(){
      fetch(API + "?action=list&page=" + encodeURIComponent(route), { credentials: "include" })
        .then(function(r){ return r.json(); })
        .then(function(d){
          if (d.items) setItems(d.items);
          if (d.replies) setReplies(d.replies);
        }).catch(function(e){ console.warn("feedback list failed", e); });
    }, [route]);

    useEffect(function(){ refresh(); }, [refresh]);

    useEffect(function(){
      fetch(API + "?action=users", { credentials: "include" })
        .then(function(r){ return r.json(); })
        .then(function(d){ setUsers(d.users || []); })
        .catch(function(){});
    }, []);

    useEffect(function(){
      if (!dropMode) return;
      const onClick = function(e){
        if (e.target.closest && e.target.closest(".mm-fb-panel, .mm-fb-fab, .mm-fb-pin, .mm-fb-drop-banner")) return;
        e.preventDefault(); e.stopPropagation();
        const x_pct = (e.clientX / window.innerWidth) * 100;
        const page_y_px = e.pageY;
        const anchor_text = getAnchorText(e.target);
        setPendingPin({ x_pct: x_pct, page_y_px: page_y_px, anchor_text: anchor_text });
        setDropMode(false);
      };
      const onEsc = function(e){ if (e.key === "Escape") setDropMode(false); };
      window.addEventListener("click", onClick, true);
      window.addEventListener("keydown", onEsc);
      return function(){
        window.removeEventListener("click", onClick, true);
        window.removeEventListener("keydown", onEsc);
      };
    }, [dropMode]);

    const pins = items.filter(function(i){ return i.kind === "pin" && i.page_route === route; });
    const pageNotes = items.filter(function(i){ return i.kind === "note_page" && i.page_route === route; });
    const siteNotes = items.filter(function(i){ return i.kind === "note_site"; });

    const repliesFor = function(id) {
      const out = [];
      const collect = function(parentId) {
        for (const r of replies) {
          if (r.parent_id === parentId) { out.push(r); collect(r.id); }
        }
      };
      collect(id);
      return out;
    };

    const numMap = useMemo(function(){ return buildNumberMap(items, replies); }, [items, replies]);

    const handleCreate = async function(payload) {
      const res = await fetch(API, {
        method: "POST", credentials: "include",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(Object.assign({ action: "create" }, payload))
      });
      const d = await res.json();
      if (!res.ok) { alert("Save failed: " + (d.error || "unknown")); return null; }
      refresh();
      return d.item;
    };

    const handleDelete = async function(id) {
      if (!confirm("Delete this comment? Replies will also be deleted.")) return;
      const res = await fetch(API, {
        method: "POST", credentials: "include",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ action: "delete", id: id })
      });
      const d = await res.json();
      if (!res.ok) { alert("Delete failed: " + (d.error || "unknown")); return; }
      if (selectedThread === id) setSelectedThread(null);
      refresh();
    };

    const handleEdit = async function(id, message) {
      const res = await fetch(API, {
        method: "POST", credentials: "include",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ action: "edit", id: id, message: message })
      });
      const d = await res.json();
      if (!res.ok) { alert("Edit failed: " + (d.error || "unknown")); return false; }
      refresh();
      return true;
    };

    const handleResolve = async function(id, resolved) {
      const res = await fetch(API, {
        method: "POST", credentials: "include",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ action: "resolve", id: id, resolved: resolved })
      });
      if (!res.ok) { alert("Resolve failed"); return; }
      refresh();
    };

    const startDropPin = function() { setDropMode(true); setOpen(false); };
    const openCount = items.filter(function(i){ return i.status === "open"; }).length;

    return (
      <>
        {pins.map(function(p, idx){
          return <PinDot key={p.id} pin={p} num={idx + 1}
            replies={repliesFor(p.id)} currentUser={currentUser}
            onClick={function(){ setSelectedThread(p.id); setOpen(true); setTab("pins"); }} />;
        })}

        {dropMode && <div className="mm-fb-drop-overlay" />}
        {dropMode && <div className="mm-fb-drop-banner">Click anywhere to drop a pin · Esc to cancel</div>}

        {pendingPin && (
          <PinComposeOverlay pendingPin={pendingPin} users={users} currentUser={currentUser}
            page_route={route}
            onCancel={function(){ setPendingPin(null); }}
            onSave={async function(msg){
              const item = await handleCreate({
                kind: "pin", page_route: route, message: msg,
                x_pct: pendingPin.x_pct, page_y_px: pendingPin.page_y_px,
                anchor_text: pendingPin.anchor_text
              });
              setPendingPin(null);
              if (item) { setOpen(true); setSelectedThread(item.id); }
            }} />
        )}

        {!dropMode && !pendingPin && (
          <button className="mm-fb-fab" onClick={function(){ setOpen(function(o){ return !o; }); }}>
            <span style={{ fontSize: 18, lineHeight: 1 }}>💬</span><span className="label">Feedback</span>
            {openCount > 0 && <span className="badge">{openCount}</span>}
          </button>
        )}

        {open && !dropMode && !pendingPin && (
          <div className="mm-fb-panel">
            <div className="mm-fb-header">
              <h3>{selectedThread ? "Thread" : ("Feedback · " + currentUser.display_name)}</h3>
              <button className="close" onClick={function(){ setOpen(false); }}>×</button>
            </div>

            {!selectedThread && (
              <>
                <div className="mm-fb-tabs">
                  <button className={tab === "pins" ? "active" : ""} onClick={function(){ setTab("pins"); }}>
                    📍 Pins · {pins.length}
                  </button>
                  <button className={tab === "notes" ? "active" : ""} onClick={function(){ setTab("notes"); }}>
                    📝 Notes · {pageNotes.length + siteNotes.length}
                  </button>
                </div>

                <div className="mm-fb-body">
                  {tab === "pins" && (
                    <>
                      <div className="mm-fb-actions">
                        <button className="mm-fb-btn primary" onClick={startDropPin}>+ Drop pin</button>
                        <span style={{ fontSize: 11, color: "var(--text-3,#8A8A86)", fontFamily: "var(--font-mono,'JetBrains Mono'),monospace" }}>
                          page: <strong>{route}</strong>
                        </span>
                      </div>
                      {pins.length === 0 && <div className="mm-fb-empty">No pins on this page yet. Drop one to leave a callout.</div>}
                      {pins.map(function(p){
                        return <ItemCard key={p.id} item={p} label={numMap[p.id]}
                          replyCount={repliesFor(p.id).length}
                          onOpen={function(){ setSelectedThread(p.id); setTimeout(function(){ locatePin(p.id); }, 100); }} />;
                      })}
                    </>
                  )}

                  {tab === "notes" && (
                    <>
                      <div className="mm-fb-actions">
                        <button className="mm-fb-btn" onClick={function(){ setComposeNoteScope("page"); }}>+ Page note</button>
                        <button className="mm-fb-btn" onClick={function(){ setComposeNoteScope("site"); }}>+ Site-wide note</button>
                      </div>

                      {composeNoteScope && (
                        <NoteCompose scope={composeNoteScope} users={users} currentUser={currentUser}
                          onCancel={function(){ setComposeNoteScope(null); }}
                          onSave={async function(msg){
                            await handleCreate({
                              kind: composeNoteScope === "page" ? "note_page" : "note_site",
                              page_route: composeNoteScope === "page" ? route : null,
                              message: msg
                            });
                            setComposeNoteScope(null);
                          }} />
                      )}

                      {pageNotes.length > 0 && <div className="mm-fb-section-label">For this page ({route})</div>}
                      {pageNotes.map(function(n){
                        return <ItemCard key={n.id} item={n} label={numMap[n.id]}
                          replyCount={repliesFor(n.id).length}
                          onOpen={function(){ setSelectedThread(n.id); }} />;
                      })}

                      {siteNotes.length > 0 && <div className="mm-fb-section-label">Site-wide</div>}
                      {siteNotes.map(function(n){
                        return <ItemCard key={n.id} item={n} label={numMap[n.id]}
                          replyCount={repliesFor(n.id).length}
                          onOpen={function(){ setSelectedThread(n.id); }} />;
                      })}

                      {pageNotes.length + siteNotes.length === 0 && !composeNoteScope && (
                        <div className="mm-fb-empty">No general notes yet. Use page notes for things like "mobile optimization needed", or site-wide for cross-cutting issues.</div>
                      )}
                    </>
                  )}
                </div>
              </>
            )}

            {selectedThread && (
              <ThreadView thread={items.find(function(i){ return i.id === selectedThread; })}
                numMap={numMap}
                onEdit={handleEdit}
                onLocate={locatePin}
                replies={repliesFor(selectedThread)}
                users={users} currentUser={currentUser}
                onBack={function(){ setSelectedThread(null); }}
                onReply={async function(msg, parentId){
                  await handleCreate({
                    kind: "pin", page_route: route,
                    parent_id: parentId, message: msg
                  });
                }}
                onDelete={handleDelete}
                onResolve={handleResolve} />
            )}
          </div>
        )}
      </>
    );
  }

  function PinDot({ pin, num, replies, currentUser, onClick }) {
    const pageW = document.documentElement.scrollWidth;
    const x = (Number(pin.x_pct || 0) / 100) * pageW;
    const y = Number(pin.page_y_px || 0);
    const directedAt = isDirectedAt(pin.message, currentUser.display_name) ||
      replies.some(function(r){ return isDirectedAt(r.message, currentUser.display_name); });
    let cls = "mm-fb-pin";
    if (pin.status === "resolved") cls += " resolved";
    if (directedAt) cls += " directed";
    return (
      <button className={cls} style={{ left: x + "px", top: y + "px" }}
        data-mm-fb-pin-id={pin.id}
        title={pin.username + ": " + (pin.message || "").slice(0, 80)}
        onClick={onClick}>
        P{num}
      </button>
    );
  }

  function ItemCard({ item, label, replyCount, onOpen }) {
    const isPin = item.kind === "pin";
    return (
      <div className={"mm-fb-item" + (item.status === "resolved" ? " resolved" : "")} onClick={onOpen}>
        <div className="meta">
          <span className={"badge" + (isPin ? "" : " note")}>{label}</span>
          <span className="author">{item.username}</span>
          <span>{fmtTime(item.created_at)}</span>
          {replyCount > 0 && <span>· {replyCount} {replyCount === 1 ? "reply" : "replies"}</span>}
          {item.status === "resolved" && <span>· ✓ resolved</span>}
        </div>
        <div className="msg">{renderMessageWithMentions(item.message)}</div>
        {item.anchor_text && <div className="anchor">near: "{item.anchor_text}"</div>}
      </div>
    );
  }

  function MessageEditor({ initial, onSave, onCancel }) {
    const [v, setV] = useState(initial || "");
    return (
      <div className="mm-fb-edit-form">
        <textarea value={v} onChange={function(e){ setV(e.target.value); }} autoFocus />
        <div className="actions">
          <button className="mm-fb-btn ghost" style={{ fontSize: 11, padding: "3px 10px" }} onClick={onCancel}>Cancel</button>
          <button className="mm-fb-btn primary" style={{ fontSize: 11, padding: "3px 10px" }} disabled={!v.trim()}
            onClick={async function(){ const ok = await onSave(v.trim()); if (ok) onCancel(); }}>Save</button>
        </div>
      </div>
    );
  }

  function ThreadView({ thread, replies, users, currentUser, numMap, onBack, onReply, onDelete, onResolve, onEdit, onLocate }) {
    const [editingId, setEditingId] = useState(null);
    if (!thread) {
      return (
        <div className="mm-fb-body">
          <button className="mm-fb-back" onClick={onBack}>← Back</button>
          <div className="mm-fb-empty">Thread not found.</div>
        </div>
      );
    }
    const canDelete = function(m){ return m.user_id === currentUser.id || currentUser.isSuperadmin; };
    const threadLabel = (numMap && numMap[thread.id]) || "";
    const isPin = thread.kind === "pin";
    return (
      <>
        <div className="mm-fb-body">
          <button className="mm-fb-back" onClick={onBack}>← Back to list</button>
          <div className="mm-fb-item" style={{ cursor: "default" }}>
            <div className="meta">
              <span className={"badge" + (isPin ? "" : " note")}>{threadLabel}</span>
              <span className="author">{thread.username}</span>
              <span>{fmtTime(thread.created_at)}</span>
              {thread.status === "resolved" && <span>· ✓ resolved</span>}
              {isPin && (
                <button className="mm-fb-locate" style={{ marginLeft: "auto" }}
                  onClick={function(){ onLocate(thread.id); }}>🎯 Locate</button>
              )}
            </div>
            {editingId === thread.id ? (
              <MessageEditor initial={thread.message}
                onCancel={function(){ setEditingId(null); }}
                onSave={async function(msg){ return await onEdit(thread.id, msg); }} />
            ) : (
              <>
                <div className="msg">{renderMessageWithMentions(thread.message)}</div>
                {thread.anchor_text && <div className="anchor">near: "{thread.anchor_text}"</div>}
                <div className="mm-fb-item-actions">
                  <button onClick={function(){ onResolve(thread.id, thread.status !== "resolved"); }}>
                    {thread.status === "resolved" ? "Reopen" : "Mark resolved"}
                  </button>
                  {canDelete(thread) && (
                    <button onClick={function(){ setEditingId(thread.id); }}>Edit</button>
                  )}
                  {canDelete(thread) && (
                    <button className="danger" onClick={function(){ onDelete(thread.id); }}>Delete</button>
                  )}
                </div>
              </>
            )}
          </div>

          {replies.map(function(r){
            const directed = isDirectedAt(r.message, currentUser.display_name);
            const replyLabel = (numMap && numMap[r.id]) || "";
            return (
              <div key={r.id} className={"mm-fb-reply" + (directed ? " directed" : "")}>
                <div style={{ display: "flex", gap: 8, fontSize: 11, color: "var(--text-3,#8A8A86)", fontFamily: "var(--font-mono,'JetBrains Mono'),monospace", flexWrap: "wrap", alignItems: "center" }}>
                  <span className="mm-fb-id">{replyLabel}</span>
                  <span style={{ fontWeight: 600, color: "var(--text-2,#4A4A48)" }}>{r.username}</span>
                  <span>{fmtTime(r.created_at)}</span>
                  {directed && <span style={{ color: "var(--mm-red,#E2231A)", fontWeight: 600 }}>· directed at you</span>}
                </div>
                {editingId === r.id ? (
                  <MessageEditor initial={r.message}
                    onCancel={function(){ setEditingId(null); }}
                    onSave={async function(msg){ return await onEdit(r.id, msg); }} />
                ) : (
                  <>
                    <div style={{ fontSize: 13, marginTop: 2, whiteSpace: "pre-wrap", wordBreak: "break-word" }}>
                      {renderMessageWithMentions(r.message)}
                    </div>
                    {canDelete(r) && (
                      <div className="mm-fb-item-actions">
                        <button onClick={function(){ setEditingId(r.id); }}>Edit</button>
                        <button className="danger" onClick={function(){ onDelete(r.id); }}>Delete</button>
                      </div>
                    )}
                  </>
                )}
              </div>
            );
          })}
        </div>

        <ReplyCompose users={users} currentUser={currentUser} parentId={thread.id} onSubmit={onReply} />
      </>
    );
  }

  function PinComposeOverlay({ pendingPin, users, currentUser, page_route, onCancel, onSave }) {
    const [msg, setMsg] = useState("");
    const taRef = useRef(null);
    useEffect(function(){ if (taRef.current) taRef.current.focus(); }, []);
    return (
      <div className="mm-fb-panel" style={{ left: "50%", right: "auto", top: "50%", bottom: "auto", transform: "translate(-50%, -50%)", zIndex: 9100 }}>
        <div className="mm-fb-header">
          <h3>📍 New pin · {currentUser.display_name}</h3>
          <button className="close" onClick={onCancel}>×</button>
        </div>
        <div style={{ padding: "8px 16px", fontSize: 11, color: "var(--text-3,#8A8A86)", fontFamily: "var(--font-mono,'JetBrains Mono'),monospace", borderBottom: "1px solid var(--border,#E5E5E0)" }}>
          page: <strong>{page_route}</strong>{pendingPin.anchor_text ? " · near: \"" + pendingPin.anchor_text + "\"" : ""}
        </div>
        <MentionTextarea ref={taRef} users={users} value={msg} onChange={setMsg}
          placeholder="What needs to change here? Use @Name to direct it." />
        <div style={{ padding: "8px 12px 12px", display: "flex", gap: 8, justifyContent: "flex-end" }}>
          <button className="mm-fb-btn ghost" onClick={onCancel}>Cancel</button>
          <button className="mm-fb-btn primary" disabled={!msg.trim()}
            onClick={function(){ if (msg.trim()) onSave(msg.trim()); }}>Save pin</button>
        </div>
      </div>
    );
  }

  function NoteCompose({ scope, users, currentUser, onCancel, onSave }) {
    const [msg, setMsg] = useState("");
    return (
      <div style={{ border: "1px solid var(--border,#E5E5E0)", borderRadius: 8, padding: 8, marginBottom: 8 }}>
        <div className="mm-fb-section-label" style={{ marginTop: 0 }}>
          New {scope === "page" ? "page note" : "site-wide note"} · {currentUser.display_name}
        </div>
        <MentionTextarea users={users} value={msg} onChange={setMsg}
          placeholder={scope === "page" ? "e.g. Mobile optimization needed on this page" : "e.g. Footer copyright is wrong everywhere"} />
        <div style={{ display: "flex", gap: 8, justifyContent: "flex-end", marginTop: 6 }}>
          <button className="mm-fb-btn ghost" onClick={onCancel}>Cancel</button>
          <button className="mm-fb-btn primary" disabled={!msg.trim()}
            onClick={function(){ if (msg.trim()) onSave(msg.trim()); }}>Add note</button>
        </div>
      </div>
    );
  }

  function ReplyCompose({ users, currentUser, parentId, onSubmit }) {
    const [msg, setMsg] = useState("");
    return (
      <div style={{ borderTop: "1px solid var(--border,#E5E5E0)" }}>
        <MentionTextarea users={users} value={msg} onChange={setMsg}
          placeholder="Reply… use @Name to direct, or leave undirected for action item" />
        <div style={{ padding: "0 12px 12px", display: "flex", gap: 8, justifyContent: "flex-end" }}>
          <button className="mm-fb-btn primary" disabled={!msg.trim()}
            onClick={async function(){
              if (!msg.trim()) return;
              await onSubmit(msg.trim(), parentId);
              setMsg("");
            }}>Reply</button>
        </div>
      </div>
    );
  }

  const MentionTextarea = React.forwardRef(function MentionTextarea({ users, value, onChange, placeholder }, ref) {
    const localRef = useRef(null);
    const taRef = ref || localRef;
    const [active, setActive] = useState(false);
    const [filter, setFilter] = useState("");
    const [highlight, setHighlight] = useState(0);
    const [mentionStart, setMentionStart] = useState(-1);
    const [dropdownPos, setDropdownPos] = useState(null);

    useEffect(function(){
      if (!active || !taRef.current) { setDropdownPos(null); return; }
      const update = function(){
        if (!taRef.current) return;
        const r = taRef.current.getBoundingClientRect();
        setDropdownPos({ left: r.left, bottom: window.innerHeight - r.top + 4, width: r.width });
      };
      update();
      window.addEventListener("resize", update);
      window.addEventListener("scroll", update, true);
      return function(){
        window.removeEventListener("resize", update);
        window.removeEventListener("scroll", update, true);
      };
    }, [active, value]);

    const matches = useMemo(function(){
      if (!active) return [];
      const q = filter.toLowerCase();
      return users.filter(function(u){ return (u.display_name || "").toLowerCase().includes(q); }).slice(0, 5);
    }, [users, filter, active]);

    const onInput = function(e) {
      const v = e.target.value;
      onChange(v);
      const cursor = e.target.selectionStart;
      const before = v.slice(0, cursor);
      const at = before.lastIndexOf("@");
      if (at >= 0) {
        const between = before.slice(at + 1);
        if (!/[\n\r]/.test(between) && between.length <= 30) {
          setActive(true); setMentionStart(at);
          setFilter(between.replace(/^"/, ""));
          setHighlight(0);
          return;
        }
      }
      setActive(false);
    };

    const insertMention = function(name) {
      const ta = taRef.current;
      if (!ta) return;
      const cursor = ta.selectionStart;
      const before = value.slice(0, mentionStart);
      const after = value.slice(cursor);
      const mention = name.indexOf(" ") >= 0 ? ("@\"" + name + "\"") : ("@" + name);
      const next = before + mention + " " + after;
      onChange(next);
      setActive(false);
      requestAnimationFrame(function(){
        const pos = (before + mention + " ").length;
        ta.setSelectionRange(pos, pos);
        ta.focus();
      });
    };

    const onKeyDown = function(e) {
      if (!active || matches.length === 0) return;
      if (e.key === "ArrowDown") { e.preventDefault(); setHighlight(function(h){ return (h + 1) % matches.length; }); }
      else if (e.key === "ArrowUp") { e.preventDefault(); setHighlight(function(h){ return (h - 1 + matches.length) % matches.length; }); }
      else if (e.key === "Enter" || e.key === "Tab") { e.preventDefault(); insertMention(matches[highlight].display_name); }
      else if (e.key === "Escape") { setActive(false); }
    };

    return (
      <div style={{ position: "relative", padding: 12 }}>
        <textarea ref={taRef} value={value} onChange={onInput} onKeyDown={onKeyDown}
          placeholder={placeholder}
          style={{ width:"100%", minHeight: 70, resize: "vertical", padding: "8px 10px",
            border: "1px solid var(--border,#E5E5E0)", borderRadius: 8, background: "var(--bg,#fff)",
            color: "var(--text,#0E0E0E)", fontFamily: "inherit", fontSize: 13, lineHeight: 1.4 }} />
        {active && matches.length > 0 && dropdownPos && (
          <div className="mm-fb-mention-list" style={{
            position: "fixed",
            left: dropdownPos.left + "px",
            bottom: dropdownPos.bottom + "px",
            width: dropdownPos.width + "px",
            right: "auto",
            top: "auto"
          }}>
            {matches.map(function(u, i){
              return (
                <button key={u.id} className={i === highlight ? "active" : ""}
                  onMouseDown={function(e){ e.preventDefault(); insertMention(u.display_name); }}>
                  @{u.display_name}<span style={{ color: "var(--text-3,#8A8A86)", marginLeft: 8, fontSize: 11 }}>{u.email}</span>
                </button>
              );
            })}
          </div>
        )}
      </div>
    );
  });

})();
