// ============ INBOX TAB ============
// 2-column shared inbox: left list of customer threads, right transcript pane.
// "Take over" pauses the agent on a thread so you can reply as yourself.
// Threads are owned by app.jsx (so the dashboard can append a thread when
// a new agent is hired); we read + mutate them via the threads / setThreads
// props.

const IX_SERVER = () =>
  (typeof window !== "undefined" && window.CITRUS_CONFIG && window.CITRUS_CONFIG.SERVER_URL) ||
  "http://localhost:3001";

// Classification tag palette — mirrors server/lead-categories.js and the Leads
// tab. Read-only here; editing lives in the Leads funnel.
const IX_CATEGORY = {
  spare_parts: { label: "Spare parts",           fg: "#1F4FB5", bg: "#EEF4FF", bd: "#A6D8FF" },
  maintenance: { label: "Maintenance / Service", fg: "#B45309", bg: "#FFF7ED", bd: "#FCD34D" },
  new_project: { label: "New project",           fg: "#065F46", bg: "#ECFDF5", bd: "#6EE7B7" },
  general:     { label: "General / Other",       fg: "#4B5563", bg: "#F3F4F6", bd: "#D1D5DB" },
};

function InboxTab({ agents, threads, setThreads }) {
  const threadsState = threads || [];
  const byId = Object.fromEntries(agents.map(a => [a.id, a]));
  // Default to All (the first chip); Unread / Read / Needs-you sit alongside it.
  const [filter, setFilter]       = useState("all");
  const [openId, setOpenId]       = useState(threadsState[0] ? threadsState[0].id : null);
  const [takenOver, setTakenOver] = useState({});
  const [draft, setDraft]         = useState("");
  const [moreOpen, setMoreOpen]   = useState(false);
  const [nameMap, setNameMap]     = useState({});
  // Scroll container for the open thread's transcript — used to jump to the
  // newest message whenever a thread is opened or a message arrives.
  const transcriptRef             = React.useRef(null);

  // Resolve raw customer IDs (tg:123, tgm:123, +1234567890) to known names.
  useEffect(() => {
    fetch(`${IX_SERVER()}/customers/memory`)
      .then(r => r.json())
      .then(data => {
        const map = {};
        for (const p of data.profiles || []) {
          if (p.name) map[p.from] = p.name;
        }
        setNameMap(map);
      })
      .catch(() => {});
  }, []);

  const resolvedName = (rawId) => nameMap[rawId] || null;
  // Display name for a thread: the agent-captured name (same source the Leads
  // list uses) wins, then a customer-memory match, then the raw phone/Telegram
  // id. hasName tells us whether to show the raw id as a secondary line.
  const displayName = (t) => (t && t.customerName) || resolvedName(t && t.customer) || (t && t.customer);
  const hasName     = (t) => !!((t && t.customerName) || resolvedName(t && t.customer));
  // Teach state — which message index has the inline correction input open,
  // and the current draft. Lets you flag any agent reply that's wrong and
  // turn your one-line note into a durable agent learning.
  const [teachIdx, setTeachIdx]   = useState(null);
  const [teachNote, setTeachNote] = useState("");
  const [teachBusy, setTeachBusy] = useState(false);
  const [investigateIdx, setInvestigateIdx] = useState(null);
  const [investigateBusyIdx, setInvestigateBusyIdx] = useState(null);
  const [investigateByIdx, setInvestigateByIdx] = useState({});
  const [routeOpen, setRouteOpen] = useState(false);

  const submitTeach = (msgIdx) => {
    if (!teachNote.trim()) { window.toast && window.toast("Write what was wrong", "warn"); return; }
    if (!open || !openAgent) return;
    setTeachBusy(true);
    const tx = open.transcript || [];
    const start = Math.max(0, msgIdx - 2);
    const ctx = tx.slice(start, msgIdx + 1)
      .map((m) => `${m.from === "customer" ? "CUSTOMER" : m.from === "you" ? "YOU" : openAgent.name.toUpperCase()}: ${m.text || ""}`)
      .join("\n");
    fetch(`${IX_SERVER()}/agents/${openAgent.id}/teach`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ note: teachNote, context: ctx }),
    })
      .then((r) => r.ok ? r.json() : r.json().then((j) => Promise.reject(j)))
      .then(() => {
        window.toast && window.toast(`${openAgent.name} just learned from that`, "good");
        setTeachIdx(null);
        setTeachNote("");
      })
      .catch((err) => window.toast && window.toast(err.error || "Couldn't save the correction", "warn"))
      .finally(() => setTeachBusy(false));
  };

  const investigateMessage = (msgIdx, msg) => {
    if (!open) return;
    setInvestigateBusyIdx(msgIdx);
    fetch(`${IX_SERVER()}/conversations/${encodeURIComponent(open.id)}/investigate-message`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ messageId: msg?.id || undefined, messageIndex: msgIdx }),
    })
      .then((r) => r.ok ? r.json() : r.json().then((j) => Promise.reject(j)))
      .then((data) => {
        setInvestigateIdx(msgIdx);
        setInvestigateByIdx((prev) => ({ ...prev, [msgIdx]: data }));
      })
      .catch((err) => window.toast && window.toast(err.error || "Couldn't investigate this reply", "warn"))
      .finally(() => setInvestigateBusyIdx(null));
  };

  // Keep openId pointing at a thread that exists. When a new thread is
  // prepended (e.g., a new agent's seeded thread), surface it as the open
  // one so the user lands on it.
  useEffect(() => {
    if (threadsState.length === 0) { setOpenId(null); return; }
    if (!threadsState.some(t => t.id === openId)) setOpenId(threadsState[0].id);
  }, [threadsState.length, threadsState[0] && threadsState[0].id]);

  // Cross-tab "open this thread" signal — fired by the Leads tab's
  // "Open conversation" button. Two delivery paths so the click works
  // regardless of mount order:
  //   1. sessionStorage("citrus_pending_inbox_thread") — set BEFORE the
  //      Inbox mounts; consume + clear on mount.
  //   2. CustomEvent("citrus-open-thread", { detail: { from } }) — fired
  //      while the Inbox is already mounted.
  // Both resolve to setOpenId(from) and clear the unread dot.
  useEffect(() => {
    const focusThread = (from) => {
      if (!from) return;
      setOpenId(from);
      setThreads((xs) => xs.map((x) => x.id === from ? { ...x, unread: false } : x));
    };
    try {
      const pending = sessionStorage.getItem("citrus_pending_inbox_thread");
      if (pending) {
        sessionStorage.removeItem("citrus_pending_inbox_thread");
        focusThread(pending);
      }
    } catch { /* ignore */ }
    const handler = (e) => focusThread(e && e.detail && e.detail.from);
    window.addEventListener("citrus-open-thread", handler);
    return () => window.removeEventListener("citrus-open-thread", handler);
  }, []);

  // Clear the unread dot when a thread is opened — locally AND on the
  // server. Without the server-side flip the badge would reappear on the
  // next /conversations fetch because the server still has c.unread = true,
  // so a refresh or a second operator would see a stale badge.
  const openThread = (id) => {
    setOpenId(id);
    setRouteOpen(false);
    setThreads((xs) => xs.map((x) => x.id === id ? { ...x, unread: false } : x));
    fetch(`${IX_SERVER()}/conversations/${encodeURIComponent(id)}/mark-read`, { method: "POST" })
      .catch(() => { /* best-effort; next refresh re-syncs if the call dropped */ });
  };

  // Inbox ordering: threads that need attention float to the top — unread
  // first, then "needs you" (human asked to take over) — and everything else
  // follows. Within every tier, most-recent activity first. lastMessageAt is
  // stamped server-side on each inbound/outbound; legacy rows without it sort
  // last within their tier.
  const recencyKey = (t) => t.lastMessageAt || "";
  const attentionRank = (t) => (t.unread ? 0 : t.status === "needs-you" ? 1 : 2);
  const sortInbox = (arr) => arr.slice().sort((a, b) => {
    const byTier = attentionRank(a) - attentionRank(b);
    if (byTier !== 0) return byTier;
    return recencyKey(b).localeCompare(recencyKey(a));
  });

  const visibleThreads = sortInbox(filter === "all"
    ? threadsState
    : filter === "unread"
      ? threadsState.filter(t => t.unread)
      : filter === "read"
        ? threadsState.filter(t => !t.unread)
        : filter === "needs-you"
          ? threadsState.filter(t => t.status === "needs-you")
          : threadsState.filter(t => t.agent === filter));

  const open       = threadsState.find(t => t.id === openId) || visibleThreads[0];
  const openAgent  = open ? byId[open.agent] : null;
  const isTaken    = open && takenOver[open.id];

  // Jump the transcript to the latest message when a thread is opened (openId
  // changes) or a new message arrives (transcript length grows). Without this
  // the pane keeps its previous scroll position, so a thread opens mid-chat
  // and the operator has to scroll down by hand.
  const openMsgCount = open && Array.isArray(open.transcript) ? open.transcript.length : 0;
  useEffect(() => {
    const el = transcriptRef.current;
    if (el) el.scrollTop = el.scrollHeight;
  }, [openId, openMsgCount]);

  const send = () => {
    if (!draft.trim() || !open) return;
    const now = new Date();
    const t = `${(now.getHours() % 12 || 12)}:${String(now.getMinutes()).padStart(2, "0")}`;
    const from = isTaken ? "you" : open.agent;
    setThreads((xs) => xs.map((x) =>
      x.id !== open.id ? x : {
        ...x,
        last: draft.trim(),
        time: "now",
        unread: false,
        status: x.status === "needs-you" ? "handled" : x.status,
        transcript: [...x.transcript, { from, text: draft.trim(), t }],
      }
    ));
    setDraft("");
  };
  const markUnread = () => {
    setThreads((xs) => xs.map((x) => x.id === open.id ? { ...x, unread: true } : x));
    setMoreOpen(false);
    window.toast("Marked unread", "info");
  };
  const deleteThread = () => {
    // Previously labelled "Archive thread" in the UI — but this handler
    // calls DELETE /conversations/:from on the server, which wipes the
    // row from BOTH in-memory state and Postgres. So the action is a
    // hard delete, not an archive. The mismatch sent operators on long
    // debugging chases ("I archived it but the convo still answers
    // bizarrely from a fresh phone") because they didn't realise the
    // button was already destructive — and conversely, operators who
    // wanted to actually delete a row gave up here because the button
    // looked like a soft hide. Rename + reword to match behaviour.
    if (!window.confirm("Delete this conversation permanently? This wipes the message history and the customer memory profile from the server. Cannot be undone.")) return;
    const SERVER_URL = (window.CITRUS_CONFIG && window.CITRUS_CONFIG.SERVER_URL) || "http://localhost:3001";
    fetch(`${SERVER_URL}/conversations/${encodeURIComponent(open.id)}`, { method: "DELETE" })
      .catch(() => {}); // best-effort; remove locally regardless
    setThreads((xs) => xs.filter((x) => x.id !== open.id));
    setMoreOpen(false);
    window.toast("Conversation deleted", "warn");
  };
  const clearAllThreads = () => {
    if (threadsState.length === 0) return;
    if (!window.confirm(`Clear all ${threadsState.length} conversation${threadsState.length !== 1 ? "s" : ""}?\n\nThis permanently deletes every message thread and all customer memory profiles from the server. Your agents' learnings are NOT affected. Cannot be undone.`)) return;
    const SERVER_URL = (window.CITRUS_CONFIG && window.CITRUS_CONFIG.SERVER_URL) || "http://localhost:3001";
    fetch(`${SERVER_URL}/conversations`, { method: "DELETE" }).catch(() => {});
    setThreads([]);
    window.toast(`Inbox cleared — ${threadsState.length} conversation${threadsState.length !== 1 ? "s" : ""} deleted`, "warn");
  };

  return (
    <div className="ix-page">
      <div className="ix-list">
        <div className="ix-filters">
          <button className={`ix-filter ${filter === "all" ? "is-on" : ""}`} onClick={() => setFilter("all")}>
            All
          </button>
          <button className={`ix-filter ${filter === "unread" ? "is-on" : ""}`} onClick={() => setFilter("unread")}>
            Unread
            {threadsState.filter(t => t.unread).length > 0 && (
              <span className="ix-filter-c" style={{
                background: "var(--accent, #E85D1A)",
                color: "white",
                marginLeft: 6,
              }}>{threadsState.filter(t => t.unread).length}</span>
            )}
          </button>
          <button className={`ix-filter ${filter === "read" ? "is-on" : ""}`} onClick={() => setFilter("read")}>
            Read
          </button>
          <button className={`ix-filter ${filter === "needs-you" ? "is-on" : ""}`} onClick={() => setFilter("needs-you")}>
            Needs you <span className="ix-filter-c">{threadsState.filter(t => t.status === "needs-you").length}</span>
          </button>
          {agents.filter(a => threadsState.some(t => t.agent === a.id)).map(a => (
            <button key={a.id} className={`ix-filter ${filter === a.id ? "is-on" : ""}`} onClick={() => setFilter(a.id)}>
              <span className="ix-filter-dot" style={{ background: a.palette.skin }} /> {a.name}
            </button>
          ))}
          {threadsState.length > 0 && (
            <button
              className="ix-filter"
              onClick={clearAllThreads}
              style={{ marginLeft: "auto", color: "var(--red, #dc2626)", opacity: 0.7 }}
            >
              Clear all
            </button>
          )}
        </div>
        <div className="ix-threads">
          {visibleThreads.length === 0 ? (
            <div className="ix-empty" style={{ padding: "20px" }}>
              {threadsState.length === 0
                ? "No threads yet."
                : filter === "unread"
                  ? "You're all caught up — no unread messages. Switch to Read or All to see the rest."
                  : filter === "read"
                    ? "No read conversations yet."
                    : "No conversations match this filter."}
            </div>
          ) : null}
          {visibleThreads.map(t => {
            const a = byId[t.agent];
            // Guard: a thread can reference an agent the dashboard hasn't
            // hydrated yet (SSE delivered the thread before /agents
            // resolved) or one that has since been deleted. Without this
            // null check, accessing a.palette.skin crashes the whole
            // inbox render tree and the dashboard goes blank — same
            // failure mode that took down /dashboard/<slug> while the
            // user was diagnosing a routing issue. Mirrors the
            // `if (!a) return null` guard already in activity.jsx:48.
            if (!a) return null;
            return (
              <button
                key={t.id}
                className={`ix-thread ${openId === t.id ? "is-open" : ""} ${t.unread ? "is-unread" : ""}`}
                onClick={() => openThread(t.id)}
              >
                <div className="ix-thread-side" style={{ background: a.palette.skin }} />
                <div className="ix-thread-body">
                  <div className="ix-thread-top">
                    <span className="ix-thread-name" style={t.unread ? { fontWeight: 700 } : null}>
                      {t.unread ? (
                        <span aria-label="Unread" title="Unread" style={{
                          display: "inline-block",
                          width: 8, height: 8,
                          borderRadius: "50%",
                          background: "var(--accent, #E85D1A)",
                          marginRight: 8,
                          verticalAlign: "middle",
                        }}/>
                      ) : null}
                      {displayName(t)}
                    </span>
                    <span className="ix-thread-time" style={t.unread ? { fontWeight: 600, color: "var(--accent, #E85D1A)" } : null}>{t.time}</span>
                  </div>
                  {hasName(t) && (
                    <div className="ix-thread-id">{t.customer}</div>
                  )}
                  <div className="ix-thread-last" style={t.unread ? { fontWeight: 600, color: "var(--ink, #111)" } : null}>{t.last}</div>
                  <div className="ix-thread-meta">
                    <span className="ix-thread-channel">{t.channel}</span>
                    <span className="ix-thread-sep">·</span>
                    <span className="ix-thread-agent">{a.name}</span>
                    <span className={`ix-thread-status ix-status-${t.status}`}>{t.tag}</span>
                    {(Array.isArray(t.categories) ? t.categories : []).map((k) => {
                      const c = IX_CATEGORY[k];
                      if (!c) return null;
                      return (
                        <span key={k} style={{ fontSize: 10, fontWeight: 600, padding: "1px 7px", borderRadius: 999, whiteSpace: "nowrap", color: c.fg, background: c.bg, border: `1px solid ${c.bd}` }}>
                          {c.label}
                        </span>
                      );
                    })}
                  </div>
                </div>
              </button>
            );
          })}
        </div>
      </div>

      <div className="ix-pane">
        {/* Also guard on openAgent. open is set the moment a thread is
            clicked, but openAgent is null until the agent record exists in
            byId. SSE can deliver a thread (and hence let you open it)
            before the dashboard has hydrated the agent that owns it, and
            without the openAgent guard the very next line crashes on
            openAgent.palette.skin — same "everything goes blank" failure
            as the thread-list crash above. */}
        {open && openAgent ? (
          <>
            <div className="ix-pane-head">
              <div>
                <div className="ix-pane-name">{displayName(open)}</div>
                {hasName(open) && (
                  <div className="ix-pane-id">{open.customer}</div>
                )}
                <div className="ix-pane-sub">
                  <span className="ix-mark" style={{ background: openAgent.palette.skin }}>{openAgent.name[0]}</span>
                  {openAgent.name} · {open.channel}
                  {isTaken ? <span className="ix-takeover-pill">you've taken over</span> : null}
                </div>
              </div>
              <div className="ix-pane-cta">
                {!isTaken ? (
                  <button className="btn btn-ghost btn-sm" onClick={() => setTakenOver({ ...takenOver, [open.id]: true })}>
                    Take over
                  </button>
                ) : (
                  <button className="btn btn-ghost btn-sm" onClick={() => setTakenOver({ ...takenOver, [open.id]: false })}>
                    Hand back to {openAgent.name}
                  </button>
                )}
                <div className="ix-more-wrap">
                  <button className="ix-pane-icon" title="More" onClick={() => setMoreOpen((v) => !v)}>⋯</button>
                  {moreOpen ? (
                    <div className="ix-more-menu" onMouseLeave={() => setMoreOpen(false)}>
                      <button className="ix-more-item" onClick={markUnread}>Mark unread</button>
                      <button className="ix-more-item ix-more-item-danger" onClick={deleteThread}>Delete conversation</button>
                    </div>
                  ) : null}
                </div>
              </div>
            </div>
            {(() => {
              const history = Array.isArray(open.routingHistory) ? open.routingHistory : [];
              const switches = history.filter(e => e.routeType === "switch");
              if (!switches.length) return null;
              const fmtTime = (iso) => {
                if (!iso) return "";
                try { return new Date(iso).toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); } catch { return ""; }
              };
              return (
                <div className="ix-route-trail">
                  <button className="ix-route-toggle" onClick={() => setRouteOpen(v => !v)}>
                    <span className="ix-route-icon">⇄</span>
                    Routed through {new Set(history.map(e => e.selectedAgentId)).size} agents
                    · {switches.length} switch{switches.length !== 1 ? "es" : ""}
                    <span className="ix-route-chevron">{routeOpen ? "▲" : "▼"}</span>
                  </button>
                  {routeOpen && (
                    <div className="ix-route-history">
                      {history.map((entry, i) => {
                        const ag = byId[entry.selectedAgentId];
                        const prevAg = entry.previousAgentId ? byId[entry.previousAgentId] : null;
                        const isSwitch = entry.routeType === "switch";
                        return (
                          <div key={i} className={`ix-route-entry${isSwitch ? " ix-route-entry-switch" : ""}`}>
                            <div className="ix-route-entry-dot" style={ag ? { background: ag.palette?.skin } : {}} />
                            <div className="ix-route-entry-body">
                              <span className="ix-route-entry-agent">
                                {isSwitch && prevAg ? `${prevAg.name} → ` : ""}{ag?.name || entry.selectedAgentId}
                              </span>
                              {entry.intent && <span className="ix-route-entry-intent">{entry.intent}</span>}
                              <span className="ix-route-entry-conf">{Math.round((entry.confidence || 0) * 100)}%</span>
                              {entry.at && <span className="ix-route-entry-time">{fmtTime(entry.at)}</span>}
                            </div>
                            <div className="ix-route-entry-reason">{entry.reason}</div>
                          </div>
                        );
                      })}
                    </div>
                  )}
                </div>
              );
            })()}
            <div className="ix-transcript" ref={transcriptRef}>
              {open.transcript.map((m, i) => {
                const isCustomer = m.from === "customer";
                const isYou      = m.from === "you";
                const isAgent    = !isCustomer && !isYou;
                const bg = isCustomer ? null : (isYou ? "#1A120B" : openAgent.palette.skin);
                const label = isCustomer ? displayName(open) : (isYou ? "You" : openAgent.name);
                const isTeaching = teachIdx === i;
                return (
                  <div key={i} className={`ix-msg ix-msg-${isCustomer ? "in" : "out"}`}>
                    <div className="ix-msg-sender" style={{
                      fontSize: 11, fontWeight: 600, color: "var(--ink-soft, #888)",
                      marginBottom: 2, padding: "0 6px",
                    }}>
                      {label}
                    </div>
                    <div
                      className="ix-msg-bubble"
                      dir="auto"
                      style={!isCustomer ? { background: bg, color: "#fff" } : null}
                    >
                      {window.renderChatMarkdown ? window.renderChatMarkdown(m.text) : m.text}
                    </div>
                    <div style={{ display: "flex", alignItems: "center", gap: 8, padding: "0 6px" }}>
                      <div className="ix-msg-time">{m.t}</div>
                      {isAgent && !isTeaching && (
                        <>
                          <button
                            onClick={() => investigateMessage(i, m)}
                            title="Investigate which learnings or documents influenced this reply"
                            style={{
                              background: "transparent", border: "none", cursor: "pointer",
                              fontSize: 11, color: "var(--ink-soft, #888)", padding: 0,
                            }}
                          >{investigateBusyIdx === i ? "…" : "🔎 investigate"}</button>
                          <button
                            onClick={() => { setTeachIdx(i); setTeachNote(""); }}
                            title="Teach the agent what was wrong with this reply"
                            style={{
                              background: "transparent", border: "none", cursor: "pointer",
                              fontSize: 11, color: "var(--ink-soft, #888)", padding: 0,
                            }}
                          >👎 teach</button>
                        </>
                      )}
                    </div>
                    {isAgent && investigateIdx === i && investigateByIdx[i] ? (
                      <div style={{ marginTop: 6, border: "1px solid var(--border, #d6d6d6)", borderRadius: 8, padding: 10, background: "var(--paper, #fff)" }}>
                        <div style={{ fontSize: 11, fontWeight: 700, color: "var(--ink-soft, #666)", textTransform: "uppercase", letterSpacing: "0.08em", marginBottom: 6 }}>
                          Reply investigation · {investigateByIdx[i]?.source || "recomputed"}
                        </div>
                        <div style={{ fontSize: 12, color: "var(--ink, #222)", lineHeight: 1.45 }}>
                          Confidence: <b>{investigateByIdx[i]?.summary?.confidence || "low"}</b>
                        </div>
                        {investigateByIdx[i]?.summary?.topInfluence ? (
                          <div style={{ marginTop: 6, fontSize: 12, color: "var(--ink, #222)", lineHeight: 1.45 }}>
                            Top influence: <b>{investigateByIdx[i].summary.topInfluence.type}</b> · {investigateByIdx[i].summary.topInfluence.label}
                            {Number.isFinite(Number(investigateByIdx[i].summary.topInfluence.score)) ? ` (${(Number(investigateByIdx[i].summary.topInfluence.score) * 100).toFixed(1)}%)` : ""}
                            {investigateByIdx[i].summary.topInfluence.evidence ? ` — ${investigateByIdx[i].summary.topInfluence.evidence}` : ""}
                          </div>
                        ) : (
                          <div style={{ marginTop: 6, fontSize: 12, color: "var(--ink-soft, #666)" }}>No strong influence candidate found for this reply.</div>
                        )}
                        {Array.isArray(investigateByIdx[i]?.provenance?.injectedLearnings) && investigateByIdx[i].provenance.injectedLearnings.length > 0 ? (
                          <div style={{ marginTop: 8 }}>
                            <div style={{ fontSize: 11, fontWeight: 700, color: "var(--ink-soft, #666)", marginBottom: 4 }}>Injected at call time</div>
                            <ul style={{ margin: 0, paddingLeft: 18, fontSize: 12, color: "var(--ink, #222)", lineHeight: 1.45 }}>
                              {investigateByIdx[i].provenance.injectedLearnings.map((x) => (
                                <li key={x.id || x.what}>
                                  {x.what}{x.why ? <span style={{ color: "var(--ink-soft, #888)" }}> — {x.why}</span> : null}
                                  {(x.timesInjected || 0) > 1 ? <span style={{ marginLeft: 6, color: "var(--ink-soft, #888)", fontSize: 11 }}>· used {x.timesInjected}×</span> : null}
                                </li>
                              ))}
                            </ul>
                          </div>
                        ) : null}
                        {Array.isArray(investigateByIdx[i]?.provenance?.influenceScores?.learnings) && investigateByIdx[i].provenance.influenceScores.learnings.length > 0 ? (
                          <div style={{ marginTop: 8 }}>
                            <div style={{ fontSize: 11, fontWeight: 700, color: "var(--ink-soft, #666)", marginBottom: 4 }}>Similarity-ranked learnings</div>
                            <ul style={{ margin: 0, paddingLeft: 18, fontSize: 12, color: "var(--ink, #222)", lineHeight: 1.45 }}>
                              {investigateByIdx[i].provenance.influenceScores.learnings.slice(0, 3).map((x) => (
                                <li key={x.id || x.label}>{x.label} ({(Number(x.score || 0) * 100).toFixed(1)}%)</li>
                              ))}
                            </ul>
                          </div>
                        ) : null}
                        {Array.isArray(investigateByIdx[i]?.provenance?.influenceScores?.knowledgeDocs) && investigateByIdx[i].provenance.influenceScores.knowledgeDocs.length > 0 ? (
                          <div style={{ marginTop: 8 }}>
                            <div style={{ fontSize: 11, fontWeight: 700, color: "var(--ink-soft, #666)", marginBottom: 4 }}>Top knowledge docs</div>
                            <ul style={{ margin: 0, paddingLeft: 18, fontSize: 12, color: "var(--ink, #222)", lineHeight: 1.45 }}>
                              {investigateByIdx[i].provenance.influenceScores.knowledgeDocs.slice(0, 3).map((x) => (
                                <li key={x.id || x.label}>{x.label} ({(Number(x.score || 0) * 100).toFixed(1)}%)</li>
                              ))}
                            </ul>
                          </div>
                        ) : null}
                      </div>
                    ) : null}
                    {isTeaching && (
                      <div style={{ marginTop: 6, display: "flex", gap: 6, alignItems: "stretch" }}>
                        <input
                          autoFocus
                          type="text"
                          placeholder="What was wrong / what should it have done instead?"
                          value={teachNote}
                          onChange={(e) => setTeachNote(e.target.value)}
                          onKeyDown={(e) => {
                            if (e.key === "Enter") submitTeach(i);
                            if (e.key === "Escape") { setTeachIdx(null); setTeachNote(""); }
                          }}
                          style={{
                            flex: 1, padding: "6px 10px", fontSize: 12,
                            border: "1px solid var(--border, #c0c0c0)", borderRadius: 6,
                          }}
                        />
                        <button
                          onClick={() => submitTeach(i)}
                          disabled={teachBusy}
                          className="btn btn-primary btn-sm"
                          style={{ opacity: teachBusy ? 0.5 : 1 }}
                        >{teachBusy ? "…" : "Save"}</button>
                        <button
                          onClick={() => { setTeachIdx(null); setTeachNote(""); }}
                          className="btn btn-ghost btn-sm"
                        >Cancel</button>
                      </div>
                    )}
                  </div>
                );
              })}
            </div>
            <div className="ix-composer">
              <textarea
                className="ix-composer-input"
                placeholder={isTaken ? `Reply as you…` : `Reply as ${openAgent.name} (or take over)`}
                value={draft}
                onChange={(e) => setDraft(e.target.value)}
                onKeyDown={(e) => { if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { e.preventDefault(); send(); } }}
                rows={2}
              />
              <div className="ix-composer-cta">
                <span className="ix-composer-hint">{isTaken ? "Sending as you" : `Sending as ${openAgent.name}`} · ⌘↵ to send</span>
                <button className="btn btn-primary btn-sm" onClick={send} disabled={!draft.trim()}>Send</button>
              </div>
            </div>
          </>
        ) : (
          <div className="ix-empty">
            {threadsState.length === 0
              ? "No conversations yet. Once you hire an agent and connect WhatsApp / Email, customer threads will land here."
              : "No conversations match."}
          </div>
        )}
      </div>
    </div>
  );
}

window.InboxTab = InboxTab;
