// Chat column — header, messages, composer
function ChatColumn({ virtualChat, messages, contextChips, setContextChips, megaOpen, setMegaOpen, megaTab, setMegaTab, composer, setComposer, model, setModel, accent }) {
  const scrollRef = useRef(null);
  useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [virtualChat]);

  const activeMessages = virtualChat ? virtualMessagesFor(virtualChat) : messages;

  return (
    <div className="flex-1 min-w-0 flex flex-col bg-white">
      {/* Virtual chat banner */}
      {virtualChat && (
        <div className="border-b border-[#e8e3d8] bg-[#fff8e6] px-6 py-2 flex items-center gap-2 text-[12.5px] text-[#7a5d00]">
          <Icon name="filter" size={14} />
          <span className="font-semibold text-[#0e0e10]">Virtual chat</span>
          <span className="text-[#a18a2c]">·</span>
          <span className="font-medium text-[#0e0e10]">{virtualChat.label}</span>
          <span className="ml-auto text-[11px] uppercase tracking-wider text-[#a18a2c]">{virtualChat.category}{virtualChat.source ? ` (Source: ${virtualChat.source})` : ""}</span>
        </div>
      )}

      {/* Messages */}
      <div ref={scrollRef} className="flex-1 overflow-y-auto">
        <div className="max-w-[760px] mx-auto px-6 py-8 flex flex-col gap-5">
          {activeMessages.map((m, i) => <MessageRow key={i} m={m} />)}
        </div>
      </div>

      {/* Composer */}
      <Composer
        contextChips={contextChips} setContextChips={setContextChips}
        megaOpen={megaOpen} setMegaOpen={setMegaOpen} megaTab={megaTab} setMegaTab={setMegaTab}
        composer={composer} setComposer={setComposer}
        model={model} setModel={setModel}
      />
    </div>
  );
}

// Virtual chat message pairs — looks like a NORMAL chat. Each entry includes a small
// "from" badge showing which original conversation the pair came from.
function virtualMessagesFor(vc) {
  const label = vc.label;
  if (vc.entityId === "story1") {
    const seq = [
      { from: "Spec review — Allow attachments in Teklens.AI", date: "May 22 · 10:14" },
      { role: "user", text: `Quick recap of where ${label} stands — what acceptance criteria are still open, and what's blocking us?` },
      { role: "skill-loaded", skill: "Story Drafter" },
      { role: "tool", tool: "jira_fetch_story", status: "completed", args: { key: "TEK-140" }, results: 1 },
      { role: "tool", tool: "rag_query", status: "completed", args: { sources: ["Engineering Wiki / Attachments", "Customer feedback / Q2"] }, results: 7 },
      { role: "assistant", body: "pmStorySummary" },

      { from: "Risk analysis for chat update", date: "May 18 · 16:02" },
      { role: "user", text: `Anything in ${label} that overlaps with TEK-345 (new chat frontend)? I'm worried about composer paste handling.` },
      { role: "skill-loaded", skill: "Component Analyzer" },
      { role: "tool", tool: "graph-analysis", status: "completed", args: { paths: ["src/composer/Dropzone.tsx", "src/chat/AttachmentRail.tsx"] }, results: 4 },
      { role: "assistant", body: "pmStoryRiskOverlap" },
      { role: "followups" },

      { from: "Q3 roadmap prioritization", date: "May 04 · 11:38" },
      { role: "user", text: `What's the RICE estimate looking like for ${label}? Should it stay in Q3?` },
      { role: "skill-loaded", skill: "Risk Analyzer" },
      { role: "assistant", body: "pmStoryRice" },
    ];
    return seq.map(s => s.from ? { role: "from-divider", ...s } : s);
  }
  // Component chats (Components category) — a structural component analysis, NOT health.
  if (vc.entityId && vc.entityId.indexOf("comp-") === 0) {
    const comp = (window.GRAPH_COMPONENTS || []).find(c => c.id === vc.entityId.replace("comp-", ""));
    const fileCount = comp ? comp.files : 12;
    const seq = [
      { from: "Streaming refactor — risk sweep", date: "May 21 · 14:30" },
      { role: "user", text: `Give me a structural read on ${label} — what does it own, what does it depend on, and where is the risk concentrated?` },
      { role: "skill-loaded", skill: "Component Analyzer" },
      { role: "tool", tool: "graph_analysis", status: "completed", args: { component: label, scope: "domain", root: "src/" }, results: fileCount },
      { role: "tool", tool: "git_log", status: "completed", args: { window: "30d", metric: "churn" }, results: 30 },
      { role: "assistant", body: "component", data: comp },

      { from: "Virtualization scroll anchoring", date: "May 18 · 09:48" },
      { role: "user", text: `If we change ${label}, which components does the change ripple into?` },
      { role: "skill-loaded", skill: "Component Analyzer" },
      { role: "tool", tool: "graph-analysis", status: "completed", args: { from: comp ? comp.id : label, direction: "dependents" }, results: comp ? comp.dependedBy.length : 0 },
      { role: "assistant", body: "component-ripple", data: comp },
      { role: "followups" },
    ];
    return seq.map(s => s.from ? { role: "from-divider", ...s } : s);
  }
  // Sprint chats (Sprints category) — fabricated content matching the sprint title/state.
  if (vc.entityId && vc.entityId.indexOf("sp-") === 0) {
    return sprintMessagesFor(vc);
  }
  // Generic PM/eng virtual thread (stories without a bespoke flow, know-how, code, …)
  const seq = [
    { from: "Spec review", date: "May 20 · 11:00" },
    { role: "user", text: `Give me the current state of ${label} and the open questions.` },
    { role: "skill-loaded", skill: "Story Drafter" },
    { role: "tool", tool: "rag_query", status: "completed", args: { sources: ["Engineering Wiki", "Product Specs"] }, results: 6 },
    { role: "assistant", body: "prose", data: { lead: `Here's where ${label} stands and what's still open.`, sections: [{ h: "Open questions", items: ["Scope boundary against adjacent work isn't fully drawn yet.", "Acceptance criteria need a measurable success metric.", "Owner for the exit gate is unconfirmed."] }], tail: "I can draft acceptance criteria or pull the related stories next." } },

    { from: "Q3 roadmap prioritization", date: "May 04 · 11:38" },
    { role: "user", text: `How does ${label} rank against the rest of the Q3 candidates?` },
    { role: "assistant", body: "prose", data: { lead: `${label} lands in the middle of the Q3 set on RICE — solid reach, moderate effort.`, sections: [], tail: "Recommend sequencing it after the multi-tenant roles work to avoid auth-middleware churn." } },
    { role: "followups" },
  ];
  return seq.map(s => s.from ? { role: "from-divider", ...s } : s);
}

// Sprint virtual threads — fabricated to match each sprint's title/state.
function sprintMessagesFor(vc) {
  const label = vc.label;
  let seq;
  if (vc.entityId === "sp-42") {
    seq = [
      { from: "Mid-sprint pulse — May 22", date: "May 22 · 09:30" },
      { role: "user", text: `Where does ${label} stand at the midpoint — are we on track to hit the sprint goal?` },
      { role: "skill-loaded", skill: "Sprint Pulse" },
      { role: "tool", tool: "jira_sprint_report", status: "completed", args: { sprint: "Sprint 42", board: "TEK" }, results: 18 },
      { role: "assistant", body: "prose", data: {
        lead: "Sprint 42 is at day 6 of 10. 34 of 55 committed points are done or in review — slightly ahead of the ideal burndown line. The sprint goal (ship attachments behind a flag) is on track.",
        sections: [{ h: "Status by lane", items: [
          "In review — TEK-140 Attachments (8 pts), waiting on the 50 MB size-limit AC sign-off.",
          "In progress — TEK-152 Multi-tenant roles (13 pts), on track.",
          "Blocked — TEK-166 Inline comments (5 pts), waiting on the canvas anchor decision.",
        ]}],
        tail: "Main risk is the TEK-166 blocker. If it isn't cleared by tomorrow, pull it and bring in TEK-184 (webhook retry) from the ready backlog.",
        sources: [{ id: "sp1", kind: "internal", icon: "jira", scope: "Sprints · TEK", source: "Jira", item: "Sprint 42 board", detail: "18 issues · 55 pts", chunks: 1 }],
      } },

      { from: "Sprint 42 planning notes", date: "May 16 · 14:00" },
      { role: "user", text: `Recap the commitments we locked for ${label}.` },
      { role: "assistant", body: "prose", data: {
        lead: "Sprint 42 committed 55 points across 18 issues, with one explicit goal: attachments shippable behind a feature flag by sprint end.",
        sections: [{ h: "Committed scope", items: [
          "TEK-140 Allow attachments (8 pts) — sprint goal",
          "TEK-152 Multi-tenant role permissions (13 pts)",
          "TEK-166 Inline comments on canvas (5 pts)",
          "9 smaller stories + 3 bugs (29 pts)",
        ]}],
        tail: "Capacity was 58 points across 5 engineers; we held 3 points of buffer for support escalations.",
      } },

      { from: "Carry-overs from Sprint 41", date: "May 16 · 11:10" },
      { role: "user", text: `What carried over into ${label} from the previous sprint?` },
      { role: "assistant", body: "prose", data: {
        lead: "Three items carried over from Sprint 41:",
        sections: [{ h: "Carried over", items: [
          "TEK-166 Inline comments (5 pts) — blocked on the anchor-model decision.",
          "TEK-131 Audit log filters (3 pts) — deprioritized mid-sprint.",
          "One S2 bug from the dark-mode launch (TBG-019).",
        ]}],
        tail: "Carry-over was ~11 points (20%), mostly attributable to the TEK-166 blocker.",
      } },
      { role: "followups" },
    ];
  } else if (vc.entityId === "sp-41") {
    seq = [
      { from: "Sprint 41 retro", date: "May 15 · 16:00" },
      { role: "user", text: `Summarize the ${label} retro — what went well and what we should change.` },
      { role: "skill-loaded", skill: "Sprint Pulse" },
      { role: "assistant", body: "prose", data: {
        lead: "Sprint 41 delivered 47 of 52 committed points (90%). The team felt the goal was clear, but mid-sprint scope creep from support hurt focus.",
        sections: [
          { h: "Went well", items: ["Streaming refactor landed without regressions.", "Pairing on the virtualization rewrite cut review time roughly in half."] },
          { h: "Change next time", items: ["Reserve explicit capacity for support escalations instead of absorbing them ad hoc.", "Split stories larger than 8 points before planning."] },
        ],
        tail: "Action items were assigned and folded into the Sprint 42 working agreements.",
      } },

      { from: "Delivered vs. committed", date: "May 15 · 15:20" },
      { role: "user", text: `What was the delivered-vs-committed gap for ${label}?` },
      { role: "assistant", body: "prose", data: {
        lead: "Committed 52, delivered 47. The 5-point gap was a single story (TEK-166) that carried into Sprint 42 due to an unresolved design dependency.",
        sections: [],
        tail: "Velocity over the last three sprints is 44 / 49 / 47 — stable around 47.",
      } },
      { role: "followups" },
    ];
  } else {
    seq = [
      { from: "Capacity for Sprint 43", date: "May 23 · 10:00" },
      { role: "user", text: `What's our realistic capacity for ${label}, and what should we pull in?` },
      { role: "skill-loaded", skill: "Capacity Planner" },
      { role: "tool", tool: "capacity_forecast", status: "completed", args: { sprint: "Sprint 43", team: "TEK" }, results: 5 },
      { role: "assistant", body: "prose", data: {
        lead: "Sprint 43 capacity is ~50 points — one engineer is out for three days and we're reserving 5 points for support. Against a 47-point trailing velocity, I'd commit 48.",
        sections: [{ h: "Recommended pull-in", items: [
          "TEK-166 Inline comments (5 pts) — unblock first.",
          "TEK-184 Webhook retry policy (8 pts).",
          "TEK-178 Audit log redesign (13 pts) — the quarter's priority.",
          "Remainder from the ready backlog, smallest-first.",
        ]}],
        tail: "Hold the sprint goal to 'audit log redesign shippable' so the team has one clear north star.",
      } },
      { role: "followups" },
    ];
  }
  return seq.map(s => s.from ? { role: "from-divider", ...s } : s);
}

// Generic, data-driven assistant body — used by sprint + generic virtual threads.
function ProseBody({ data }) {
  if (!data) return null;
  return (
    <div>
      <div className="text-[14px] leading-[1.65] text-[#1a1a1a]">
        {data.lead && <p>{data.lead}</p>}
        {(data.sections || []).map((s, i) => (
          <React.Fragment key={i}>
            {s.h && <h4 className="mt-3 font-semibold">{s.h}</h4>}
            {s.items && s.items.length > 0 && (
              <ol className="mt-1 ml-5 list-decimal space-y-1.5 marker:text-[#9aa0a6]">
                {s.items.map((it, j) => <li key={j}>{it}</li>)}
              </ol>
            )}
          </React.Fragment>
        ))}
        {data.tail && <p className="mt-3">{data.tail}</p>}
      </div>
      {data.sources && <Sources items={data.sources} />}
    </div>
  );
}

function MessageRow({ m }) {
  if (m.role === "user") {
    return (
      <div className="flex justify-end">
        <div className="max-w-[80%] bg-[#f4f1ea] rounded-2xl rounded-tr-sm px-4 py-2.5 text-[14px] text-[#1a1a1a] leading-relaxed border border-[#ece7da]">{m.text}</div>
      </div>
    );
  }
  if (m.role === "skill-loaded") {
    return (
      <div className="flex items-center gap-2 text-[12px] text-[#6b6b6b]">
        <Icon name="wand" size={13} className="text-[#8b5cf6]" />
        <span>Loaded skill</span>
        <span className="font-medium text-[#0e0e10]">{m.skill}</span>
      </div>
    );
  }
  if (m.role === "tool") return <ToolCall {...m} />;
  if (m.role === "assistant") return <AssistantBody body={m.body} data={m.data} />;
  if (m.role === "followups") return <FollowUps />;
  if (m.role === "followups-risk") return <FollowUpsRisk />;
  if (m.role === "from-divider") return (
    <div className="flex items-center gap-2 mt-3">
      <div className="flex-1 h-px bg-[#ece7da]" />
      <span className="text-[10.5px] uppercase tracking-wider text-[#9aa0a6] font-medium">From</span>
      <span className="text-[11.5px] text-[#3a3a3a] font-medium">{m.from}</span>
      <span className="text-[11px] text-[#9aa0a6]">· {m.date}</span>
      <div className="flex-1 h-px bg-[#ece7da]" />
    </div>
  );
  return null;
}

function ToolCall({ tool, status, args, results }) {
  const [open, setOpen] = useState(false);
  return (
    <div className="border border-[#ece7da] rounded-lg bg-white">
      <button onClick={() => setOpen(o => !o)} className="w-full flex items-center gap-2 px-3 py-2 text-left">
        <Icon name="tool" size={14} className="text-[#5a5a5a]" />
        <span className="font-mono text-[12.5px] text-[#0e0e10]">{tool}</span>
        <span className="ml-1 inline-flex items-center gap-1 text-[11px] px-1.5 py-0.5 rounded-full bg-[#e8f6ec] text-[#1f7a3a]">
          <Icon name="check" size={11} /> Completed
        </span>
        <span className="ml-auto text-[11.5px] text-[#9aa0a6]">{results} results</span>
        <Icon name={open ? "chevron-down" : "chevron-right"} size={14} className="text-[#9aa0a6]" />
      </button>
      {open && (
        <div className="border-t border-[#ece7da] px-3 py-2 bg-[#fafaf7] font-mono text-[11.5px] text-[#3a3a3a]">
          <div className="text-[#9aa0a6] mb-1">arguments</div>
          <pre className="whitespace-pre-wrap">{JSON.stringify(args, null, 2)}</pre>
        </div>
      )}
    </div>
  );
}

function compNameOf(id) {
  const c = (window.GRAPH_COMPONENTS || []).find(x => x.id === id);
  return c ? c.name : id;
}
function compSevStyle(sev) {
  if (sev === "high") return { bg: "#fdecea", color: "#a83228", label: "High" };
  if (sev === "medium") return { bg: "#fef0d9", color: "#a05a00", label: "Medium" };
  return { bg: "#e8f6ec", color: "#1f7a3a", label: "Low" };
}
function ComponentReadBody({ comp }) {
  if (!comp) return null;
  const cx = comp.complexity;
  const cxStyle = cx === "complex" ? { bg: "#fdecea", color: "#a83228" }
    : cx === "moderate" ? { bg: "#fef0d9", color: "#a05a00" }
    : { bg: "#e8f6ec", color: "#1f7a3a" };
  return (
    <div className="text-[14px] leading-[1.65] text-[#1a1a1a]">
      <p>
        <strong>{comp.name}</strong> is a{" "}
        <span className="px-1.5 py-[1px] rounded-full text-[12px] font-medium align-middle" style={{ background: cxStyle.bg, color: cxStyle.color }}>{cx}</span>{" "}
        domain spanning <strong>{comp.files} files</strong>. {comp.desc}
      </p>

      <h4 className="mt-3 font-semibold">Owns</h4>
      <div className="mt-1 flex flex-wrap gap-1.5">
        {comp.entities.map(e => <span key={e} className="font-mono text-[12px] px-1.5 py-[1px] rounded bg-[#f4f1ea] border border-[#ece7da]">{e}</span>)}
      </div>
      <ul className="mt-2 ml-5 list-disc space-y-1 marker:text-[#9aa0a6]">
        {comp.rules.slice(0, 2).map((r, i) => <li key={i}>{r}</li>)}
      </ul>

      <h4 className="mt-3 font-semibold">Depends on</h4>
      {comp.dependsOn.length === 0
        ? <p className="mt-1 text-[#6b6b6b]">Nothing — this is a leaf domain.</p>
        : <ol className="mt-1 ml-5 list-decimal space-y-1.5 marker:text-[#9aa0a6]">
            {comp.dependsOn.map(d => <li key={d.id}><strong>{compNameOf(d.id)}.</strong> {d.note}</li>)}
          </ol>}

      <h4 className="mt-3 font-semibold">Risk concentration</h4>
      {comp.risks.length === 0
        ? <p className="mt-1 text-[#6b6b6b]">No open analyses flag this domain.</p>
        : <div className="mt-1.5 flex flex-col gap-1.5">
            {comp.risks.map((r, i) => {
              const s = compSevStyle(r.sev);
              return (
                <div key={i} className="flex items-start gap-2">
                  <span className="px-2 py-[1px] rounded-full text-[11px] font-medium shrink-0" style={{ background: s.bg, color: s.color }}>{s.label}</span>
                  <span className="flex-1">{r.title}</span>
                  <span className="text-[12px] text-[#9aa0a6] tabular-nums shrink-0">{r.score}/100</span>
                </div>
              );
            })}
          </div>}
      <p className="mt-3 text-[13px] text-[#6b6b6b]">Open the <strong>Component Analysis</strong> graph on the right to see {comp.name} in context — heat marks the blast radius across neighboring domains.</p>
    </div>
  );
}
function ComponentRippleBody({ comp }) {
  if (!comp) return null;
  return (
    <div className="text-[14px] leading-[1.65] text-[#1a1a1a]">
      {comp.dependedBy.length === 0
        ? <p>Nothing depends on <strong>{comp.name}</strong> directly — a change here is self-contained at the domain boundary.</p>
        : <>
            <p>A change in <strong>{comp.name}</strong> ripples into <strong>{comp.dependedBy.length} downstream {comp.dependedBy.length === 1 ? "domain" : "domains"}</strong>:</p>
            <ol className="mt-1 ml-5 list-decimal space-y-1.5 marker:text-[#9aa0a6]">
              {comp.dependedBy.map(d => <li key={d.id}><strong>{compNameOf(d.id)}.</strong> {d.note}</li>)}
            </ol>
          </>}
      <p className="mt-3">Recommend pinning <strong>{comp.name}</strong> as context before refactoring so retrieval stays scoped to the affected files.</p>
    </div>
  );
}

function AssistantBody({ body, data }) {
  if (body === "component") return <ComponentReadBody comp={data} />;
  if (body === "component-ripple") return <ComponentRippleBody comp={data} />;
  if (body === "prose") return <ProseBody data={data} />;
  if (body === "pmGeneric") {
    return (
      <div>
        <div className="text-[14px] leading-[1.65] text-[#1a1a1a]">
          <p>Here's the current state and the next concrete step.</p>
          <h4 className="mt-3 font-semibold">Where it stands</h4>
          <ol className="mt-1 ml-5 list-decimal space-y-1.5 marker:text-[#9aa0a6]">
            <li>The story is refined with acceptance criteria drafted; two ACs still need sign-off.</li>
            <li>No code has landed yet — it's queued behind the current sprint's in-flight work.</li>
            <li>No blocking dependencies, but it shares a code surface with TEK-345.</li>
          </ol>
          <p className="mt-3">Next step: confirm the open acceptance criteria so it can enter the next sprint cleanly. I can draft them now.</p>
        </div>
        <Sources items={SOURCES_PM_STORY} />
      </div>
    );
  }
  if (body === "pmStorySummary") {
    return (
      <div>
        <div className="text-[14px] leading-[1.65] text-[#1a1a1a]">
          <p>
            <span className="font-mono text-[12.5px] px-1 py-[1px] bg-[#f4f1ea] rounded">TEK-140</span> sits at <strong>57% of acceptance criteria signed off</strong>, with three open items left on the spec.
            The story has moved through Discovery → Refinement → In Progress, and is currently parked on the <em>QA hand-off</em> step waiting on a clarified upload-size limit.
          </p>
          <h4 className="mt-3 font-semibold">Open acceptance criteria</h4>
          <ol className="mt-1 ml-5 list-decimal space-y-1.5 marker:text-[#9aa0a6]">
            <li><strong>AC-5 · Mobile upload from camera roll.</strong> iOS picker behavior unconfirmed — needs a one-line decision: native picker only, or also files.app?</li>
            <li><strong>AC-7 · File-size ceiling.</strong> Engineering proposed 25 MB; product hasn't signed off. Support has 4 tickets requesting 50 MB.</li>
            <li><strong>AC-9 · Virus scan + quarantine UX.</strong> Spec doesn't define the user-visible state for a file pending scan.</li>
          </ol>
          <p className="mt-3">
            Blocker right now is AC-7. I recommend confirming 50 MB and re-routing AC-9 into a follow-up story so we don't slip this sprint.
          </p>
        </div>
        <Sources items={SOURCES_PM_STORY} />
      </div>
    );
  }
  if (body === "pmStoryRiskOverlap") {
    return (
      <div>
        <div className="text-[14px] leading-[1.65] text-[#1a1a1a]">
          <p>
            Yes — there's one direct collision and one indirect one.
          </p>
          <ol className="mt-2 ml-5 list-decimal space-y-1.5 marker:text-[#9aa0a6]">
            <li><strong>Composer Dropzone.</strong> Both stories modify <span className="font-mono text-[12.5px]">src/composer/Dropzone.tsx</span>. TEK-345 scopes paste handling; TEK-140 adds the drag-drop attachment flow. Last-merged-wins.</li>
            <li><strong>Attachment rail.</strong> TEK-140 introduces <span className="font-mono text-[12.5px]">AttachmentRail.tsx</span> below the composer, but TEK-345's virtualization rewrite moves the composer mount. Coordinate landing order.</li>
          </ol>
          <p className="mt-3">
            Suggest pairing the two PRs through the same code review and shipping behind the same feature flag so we can roll back together if needed.
          </p>
        </div>
        <Sources items={SOURCES_PM_STORY_OVERLAP} />
      </div>
    );
  }
  if (body === "pmStoryRice") {
    return (
      <div>
        <div className="text-[14px] leading-[1.65] text-[#1a1a1a]">
          <p>
            RICE comes out at <strong>R6 · I8 · C5 · E5 → 9.6</strong>, putting TEK-140 in the upper third of the Q3 candidate set.
            Reach is constrained by the desktop-first rollout; impact ranks high because it unblocks 4 of the 12 customer-asked workflows in your backlog.
          </p>
          <p className="mt-2">My recommendation: hold it in Q3, sequenced after TEK-152 (multi-tenant roles) to avoid the auth-middleware churn.</p>
        </div>
        <Sources items={SOURCES_PM_STORY_RICE} />
      </div>
    );
  }
  if (body === "riskMain") {
    return (
      <div>
        <div className="text-[14px] leading-[1.65] text-[#1a1a1a]">
        <p>I ran static analysis against <span className="font-mono text-[12.5px]">features/new-chat-frontend</span> and cross-referenced TEK-345's acceptance criteria. Overall implementation risk for this rollout is <strong className="text-[#c2410c]">Moderate-High (68/100)</strong> — driven primarily by the streaming-state refactor and the message-virtualization rewrite, both touching the same store.</p>
        <p className="mt-3">Top risks (by combined likelihood × blast radius):</p>
        <ol className="mt-1 ml-5 list-decimal space-y-1.5 marker:text-[#9aa0a6]">
          <li><strong>Race condition between streaming chunks and optimistic user messages.</strong> The new <span className="font-mono text-[12.5px]">useChatStream</span> hook subscribes before the optimistic insert resolves — under poor network, chunks can arrive against a stale message id.</li>
          <li><strong>Virtualization breaks scroll-to-bottom on tool-call expansion.</strong> Variable-height tool cards aren't measured before the next render; the auto-scroll hook lands above the latest message ~12% of the time in repro.</li>
          <li><strong>Migration of pinned-context store from <span className="font-mono text-[12.5px]">contextChips[]</span> to <span className="font-mono text-[12.5px]">contextGraph</span>.</strong> No backfill written. Existing users would lose pins on first load.</li>
          <li><strong>Composer drag-drop swallows clipboard paste on Safari 17.</strong> File handler intercepts paste events globally rather than scoped to the dropzone.</li>
        </ol>
        <p className="mt-3">Mitigations are detailed in the Risk Analysis widget on the right — each top risk has a specific code-level remediation and a suggested owner.</p>
        </div>
        <Sources items={SOURCES_RISK} />
      </div>
    );
  }
  return null;
}

const SOURCES_PM_STORY = [
  { id: "p1", kind: "internal", icon: "jira", scope: "Stories · TEK", source: "Jira", item: "TEK-140 · Allow attachments in Teklens.AI", detail: "Spec v3 · 9 ACs (5 done)", chunks: 1 },
  { id: "p2", kind: "internal", icon: "file", scope: "Know-how · Engineering Wiki", source: "Doc", item: "Attachments architecture", detail: "§ Upload path", chunks: 3 },
  { id: "p3", kind: "internal", icon: "message", scope: "Stories · TEK-140", source: "Chat", item: "Backend storage spec", detail: "May 14", chunks: 1 },
];
const SOURCES_PM_STORY_OVERLAP = [
  { id: "po1", kind: "internal", icon: "code", scope: "Code · contem/teklens-ai", source: "File", item: "src/composer/Dropzone.tsx", detail: "L22–L40 · paste handler", chunks: 2 },
  { id: "po2", kind: "internal", icon: "code", scope: "Code · contem/teklens-ai", source: "File", item: "src/chat/AttachmentRail.tsx", detail: "new component", chunks: 1 },
  { id: "po3", kind: "internal", icon: "jira", scope: "Stories · TEK", source: "Jira", item: "TEK-345 · Implement new chat frontend", detail: "Acceptance criteria · 7 items", chunks: 1 },
];
const SOURCES_PM_STORY_RICE = [
  { id: "pr1", kind: "internal", icon: "grid", scope: "Strategy · Q3", source: "Sheet", item: "Q3 RICE matrix", detail: "Row TEK-140 · 9.6", chunks: 1 },
  { id: "pr2", kind: "internal", icon: "message", scope: "Stories · TEK-140", source: "Chat", item: "Mobile upload flow review", detail: "May 9", chunks: 1 },
];
const SOURCES_RISK = [
  { id: "r1", kind: "internal", icon: "code", scope: "Code · contem/teklens-ai", source: "File", item: "src/chat/useChatStream.ts", detail: "L42–L78 · subscribe / replay logic", chunks: 3 },
  { id: "r2", kind: "internal", icon: "code", scope: "Code · contem/teklens-ai", source: "File", item: "src/chat/MessageList.tsx", detail: "L120–L165 · scroll anchor", chunks: 2 },
  { id: "r3", kind: "internal", icon: "code", scope: "Code · contem/teklens-ai", source: "File", item: "src/state/contextGraph.ts", detail: "schema · no migrator present", chunks: 4 },
  { id: "r4", kind: "internal", icon: "code", scope: "Code · contem/teklens-ai", source: "File", item: "src/composer/Dropzone.tsx", detail: "L22–L40 · paste handler", chunks: 1 },
  { id: "r5", kind: "internal", icon: "story", scope: "Stories · TEK", source: "Story", item: "TEK-345 — Implement new chat frontend", detail: "Acceptance criteria · 7 items", chunks: 1 },
  { id: "r6", kind: "internal", icon: "file", scope: "Know-how · Engineering Wiki", source: "Doc", item: "Streaming-state architecture (v2)", detail: "§ Optimistic inserts", chunks: 2 },
  { id: "r7", kind: "external", icon: "globe", source: "Web search", item: "github.com/TanStack/virtual · issue #842", detail: "scroll anchoring with variable rows" },
];

function Sources({ items }) {
  const [open, setOpen] = useState(false);
  if (!items || items.length === 0) return null;
  const internal = items.filter(s => s.kind === "internal");
  const external = items.filter(s => s.kind === "external");
  return (
    <div className="mt-3 border-t border-[#f0ebdd] pt-2">
      <button onClick={() => setOpen(o => !o)}
        className="flex items-center gap-1.5 text-[11.5px] text-[#6b6b6b] hover:text-[#0e0e10]">
        <Icon name="book" size={12} />
        <span><span className="font-medium text-[#1a1a1a]">{items.length}</span> sources</span>
        {internal.length > 0 && <span className="text-[#9aa0a6]">· {internal.length} internal</span>}
        {external.length > 0 && <span className="text-[#9aa0a6]">· {external.length} external</span>}
        <Icon name={open ? "chevron-up" : "chevron-down"} size={11} className="ml-0.5" />
      </button>
      {open && (
        <div className="mt-2 flex flex-col gap-1">
          {internal.length > 0 && (
            <>
              <div className="text-[10.5px] uppercase tracking-wider text-[#9aa0a6] mt-1 mb-0.5">Internal</div>
              {internal.map(s => <SourceRow key={s.id} {...s} />)}
            </>
          )}
          {external.length > 0 && (
            <>
              <div className="text-[10.5px] uppercase tracking-wider text-[#9aa0a6] mt-2 mb-0.5">External</div>
              {external.map(s => <SourceRow key={s.id} {...s} />)}
            </>
          )}
        </div>
      )}
    </div>
  );
}

function SourceRow({ kind, icon, scope, source, item, detail, chunks }) {
  const tint = kind === "external" ? "#3b5cf6" : "#6b6b6b";
  const bg = kind === "external" ? "#eef0ff" : "#f4f1ea";
  return (
    <div className="flex items-start gap-2.5 px-2.5 py-2 rounded-md border border-[#ece7da] bg-white hover:border-[var(--accent)]/40 hover:bg-[#fafaf7] cursor-pointer">
      <div className="w-7 h-7 shrink-0 rounded-md flex items-center justify-center" style={{ background: bg, color: tint }}>
        <Icon name={icon} size={13} />
      </div>
      <div className="flex-1 min-w-0">
        <div className="flex items-center gap-1.5 flex-wrap">
          <span className="text-[12.5px] font-medium text-[#0e0e10] truncate">{item}</span>
          {chunks ? <span className="text-[10px] tabular-nums text-[#9aa0a6] px-1 py-[1px] rounded bg-[#f4f1ea]">{chunks} chunk{chunks > 1 ? "s" : ""}</span> : null}
        </div>
        <div className="flex items-center gap-1.5 text-[11px] text-[#6b6b6b] mt-0.5 flex-wrap">
          <span className="font-medium" style={{ color: kind === "external" ? "#3b5cf6" : "#0e0e10" }}>{source}</span>
          {scope && <><span className="text-[#cbcbcb]">·</span><span className="truncate">{scope}</span></>}
          {detail && <><span className="text-[#cbcbcb]">·</span><span className="truncate text-[#9aa0a6]">{detail}</span></>}
        </div>
      </div>
      <Icon name="external" size={12} className="text-[#9aa0a6] mt-1" />
    </div>
  );
}

function FollowUpsRisk() {
  const items = [
    { icon: "branch", label: "Walk through risk #1 in code" },
    { icon: "edit", label: "Draft mitigation tickets" },
    { icon: "lab", label: "Generate test plan for migration" },
  ];
  return (
    <div className="flex flex-wrap gap-2 mt-1">
      {items.map(it => (
        <button key={it.label} className="flex items-center gap-1.5 pl-2.5 pr-3 py-1.5 rounded-full border border-[#ece7da] bg-white text-[12.5px] text-[#1a1a1a] hover:border-[var(--accent)] hover:text-[var(--accent)]">
          <Icon name={it.icon} size={13} /> {it.label}
        </button>
      ))}
    </div>
  );
}
function FollowUps() {
  const items = [
    { icon: "edit", label: "Draft acceptance criteria" },
    { icon: "lab", label: "Generate a test plan" },
    { icon: "search", label: "Find related stories" },
  ];
  return (
    <div className="flex flex-wrap gap-2 mt-1">
      {items.map(it => (
        <button key={it.label} className="flex items-center gap-1.5 pl-2.5 pr-3 py-1.5 rounded-full border border-[#ece7da] bg-white text-[12.5px] text-[#1a1a1a] hover:border-[var(--accent)] hover:text-[var(--accent)]">
          <Icon name={it.icon} size={13} /> {it.label}
        </button>
      ))}
    </div>
  );
}

// ---------- COMPOSER ----------
function Composer({ contextChips, setContextChips, megaOpen, setMegaOpen, megaTab, setMegaTab, composer, setComposer, model, setModel }) {
  const [modelOpen, setModelOpen] = useState(false);
  const removeChip = (id) => setContextChips(cs => cs.filter(c => c.id !== id));
  const toggleRestricted = (id) => setContextChips(cs => cs.map(c => c.id === id ? { ...c, restricted: !c.restricted } : c));
  return (
    <div className="border-t border-[#e8e3d8] bg-[#faf8f3]">
      <div className="max-w-[760px] mx-auto px-4 py-3">
        <div className="bg-white rounded-2xl border border-[#ece7da] shadow-[0_1px_2px_rgba(0,0,0,0.03)] focus-within:border-[var(--accent)]">
          {/* chips */}
          {contextChips.length > 0 && (
            <div className="flex flex-wrap items-center gap-1.5 px-3 pt-2.5">
              {contextChips.map((chip, i) => {
                const next = contextChips[i + 1];
                const linkedNext = chip.linkedTo && next && next.id === chip.linkedTo ? next.label : null;
                const isLinkedFromPrev = i > 0 && contextChips[i - 1].linkedTo === chip.id;
                return (
                  <React.Fragment key={chip.id}>
                    <ContextChip chip={chip} onRemove={() => removeChip(chip.id)} onToggleRestricted={() => toggleRestricted(chip.id)} linkedNext={linkedNext} />
                    {linkedNext && (
                      <span className="inline-flex items-center text-[#0e7c66] -mx-0.5" title="Story references this branch — auto-pinned together">
                        <Icon name="link" size={12} />
                      </span>
                    )}
                  </React.Fragment>
                );
              })}
            </div>
          )}
          {/* input */}
          <textarea
            value={composer} onChange={e => setComposer(e.target.value)}
            placeholder="Ask Teklens anything…"
            className="w-full resize-none px-4 pt-2.5 pb-2 text-[14px] outline-none bg-transparent text-[#0e0e10] placeholder:text-[#9aa0a6]"
            rows={1} style={{ minHeight: 38 }}
          />
          {/* bottom bar */}
          <div className="flex items-center gap-1 px-2.5 pb-2">
            {/* LEFT cluster */}
            <div className="relative">
              <button onClick={() => setMegaOpen(o => !o)} className="p-1.5 rounded-md text-[#5a5a5a] hover:bg-black/[0.04] hover:text-[#0e0e10]" title="Add context">
                <Icon name="plus" size={16} />
              </button>
              {megaOpen && <MegaDropdown onClose={() => setMegaOpen(false)} tab={megaTab} setTab={setMegaTab} contextChips={contextChips} setContextChips={setContextChips} />}
            </div>
            <button className="p-1.5 rounded-md text-[#5a5a5a] hover:bg-black/[0.04] hover:text-[#0e0e10]" title="Attach file"><Icon name="paperclip" size={16} /></button>
            <FilterButton />
            <BranchButton />

            <div className="flex-1" />

            {/* RIGHT cluster */}
            <button className="p-1.5 rounded-md text-[#5a5a5a] hover:bg-black/[0.04] hover:text-[#0e0e10]" title="Voice"><Icon name="mic" size={16} /></button>
            <div className="relative">
              <button onClick={() => setModelOpen(o => !o)} className="flex items-center gap-1.5 px-2 py-1 rounded-md text-[12.5px] text-[#3a3a3a] hover:bg-black/[0.04]">
                {(() => { const cur = MODELS_LIST.find(x => x.label === model) || MODELS_LIST[0]; return (
                  <span className="relative inline-flex items-center justify-center w-4 h-4 text-[#3a3a3a]">
                    <Icon name={cur.provider} size={13} />
                    {cur.anonymous && <span className="absolute -bottom-1 -right-1 w-2.5 h-2.5 rounded-full bg-[#0e0e10] text-white flex items-center justify-center"><Icon name="shield" size={7} stroke={2.4} /></span>}
                  </span>
                ); })()}
                {model}
                <Icon name="chevron-down" size={12} className="text-[#9aa0a6]" />
              </button>
              {modelOpen && (
                <div className="absolute right-0 bottom-full mb-1 w-[300px] bg-white rounded-lg border border-[#ece7da] shadow-lg py-1 z-30 max-h-[360px] overflow-y-auto">
                  {MODELS_LIST.map(m => (
                    <button key={m.id} onClick={() => { setModel(m.label); setModelOpen(false); }} className="w-full flex items-center gap-2 px-2.5 py-1.5 text-[12.5px] hover:bg-black/[0.04]">
                      <span className="relative inline-flex items-center justify-center w-5 h-5 rounded bg-[#f4f1ea] text-[#3a3a3a]">
                        <Icon name={m.provider} size={13} />
                        {m.anonymous && <span className="absolute -bottom-1 -right-1 w-3 h-3 rounded-full bg-[#0e0e10] text-white flex items-center justify-center"><Icon name="shield" size={8} stroke={2.4} /></span>}
                      </span>
                      <span className="flex-1 text-left truncate">{m.label}</span>
                      {m.anonymous && <span className="text-[9px] font-semibold tracking-wider uppercase px-1 py-[1px] rounded bg-[#0e0e10] text-white">Anon</span>}
                      {m.label === model && <Icon name="check" size={13} className="text-[var(--accent)]" />}
                    </button>
                  ))}
                </div>
              )}
            </div>
            <button className="ml-1 w-8 h-8 rounded-full bg-[var(--accent)] text-white flex items-center justify-center hover:brightness-110">
              <Icon name="send" size={15} />
            </button>
          </div>
        </div>
      </div>
    </div>
  );
}

const MODELS_LIST = [
  { id: "gemini-3pro", label: "Gemini 3.1 Pro", provider: "gemini", sub: "Reasoning · 2M ctx" },
  { id: "gemini-3flash", label: "Gemini 3.1 Flash", provider: "gemini", sub: "Fast · 1M ctx" },
  { id: "gemini-2flashlite", label: "Gemini 2.5 Flash Lite", provider: "gemini", sub: "Cheap · 200K ctx" },
  { id: "claude-sonnet", label: "Claude Sonnet 4.6", provider: "anthropic", sub: "Reasoning · 200K ctx" },
  { id: "claude-haiku", label: "Claude Haiku 4.5", provider: "anthropic", sub: "Fast · 200K ctx" },
  { id: "gpt5", label: "GPT-5.1 Reasoning", provider: "openai", sub: "Top-tier · 400K ctx" },
  { id: "gpt5mini", label: "GPT-5.1 Mini", provider: "openai", sub: "Balanced" },
  { id: "grok4", label: "Grok 4 Heavy", provider: "grok", sub: "xAI · 256K ctx" },
  { id: "glm51", label: "GLM 5.1", provider: "cerebras", sub: "Cerebras · ultra-fast" },
  { id: "anon-sonnet", label: "Anonymized Claude Sonnet 4.6", provider: "anthropic", anonymous: true, sub: "Routed via proxy · zero retention" },
  { id: "anon-gpt5", label: "Anonymized GPT-5.1", provider: "openai", anonymous: true, sub: "Routed via proxy · zero retention" },
  { id: "anon-gemini", label: "Anonymized Gemini 3.1 Pro", provider: "gemini", anonymous: true, sub: "Routed via proxy · zero retention" },
];

function BranchButton() {
  const [open, setOpen] = useState(false);
  const [active, setActive] = useState("main");
  const branches = [
    { id: "main", label: "main", sub: "Default · 14 messages", current: true, badge: "current" },
    { id: "salt-deepdive", label: "salt-deepdive", sub: "Branched at msg 4 · 6 messages · 2h ago" },
    { id: "biomarker-explore", label: "biomarker-explore", sub: "Branched at msg 7 · 11 messages · yesterday" },
    { id: "vo2-sidetrack", label: "vo2-sidetrack", sub: "Branched at msg 9 · 3 messages · 3d ago" },
  ];
  const cur = branches.find(b => b.id === active) || branches[0];
  return (
    <div className="relative">
      <button onClick={() => setOpen(o => !o)}
        className="flex items-center gap-1 pl-1.5 pr-1.5 py-1.5 rounded-md text-[#5a5a5a] hover:bg-black/[0.04] hover:text-[#0e0e10]"
        title="Switch branch">
        <Icon name="branch" size={16} />
        <span className="text-[11.5px] font-medium text-[#3a3a3a] max-w-[110px] truncate">{cur.label}</span>
        <Icon name="chevron-down" size={11} className="text-[#9aa0a6]" />
      </button>
      {open && (
        <>
          <div className="fixed inset-0 z-20" onClick={() => setOpen(false)} />
          <div className="absolute left-0 bottom-full mb-1.5 w-[320px] bg-white rounded-xl border border-[#ece7da] shadow-2xl z-30 overflow-hidden">
            <div className="px-3 py-2 border-b border-[#ece7da] flex items-center gap-2">
              <Icon name="branch" size={13} className="text-[var(--accent)]" />
              <div className="text-[12.5px] font-semibold text-[#0e0e10]">Branches</div>
              <span className="text-[11px] text-[#9aa0a6]">in this chat</span>
            </div>
            <div className="max-h-[260px] overflow-y-auto py-1">
              {branches.map(b => (
                <button key={b.id} onClick={() => { setActive(b.id); setOpen(false); }}
                  className={`w-full flex items-start gap-2.5 px-3 py-2 text-left hover:bg-[#faf7f0] ${active === b.id ? "bg-[#faf7f0]" : ""}`}>
                  <Icon name="branch" size={13} className="mt-0.5 text-[#9aa0a6]" />
                  <div className="flex-1 min-w-0">
                    <div className="flex items-center gap-1.5">
                      <span className="font-mono text-[12.5px] text-[#0e0e10] truncate">{b.label}</span>
                      {b.badge && <span className="text-[9.5px] font-semibold tracking-wider uppercase px-1 py-[1px] rounded bg-[#0e0e10] text-white">{b.badge}</span>}
                    </div>
                    <div className="text-[11px] text-[#9aa0a6] truncate">{b.sub}</div>
                  </div>
                  {active === b.id && <Icon name="check" size={13} className="text-[var(--accent)] mt-0.5" />}
                </button>
              ))}
            </div>
            <div className="border-t border-[#ece7da] px-2 py-1.5 bg-[#faf8f3] flex items-center gap-1">
              <button className="flex items-center gap-1.5 px-2 py-1 rounded text-[12px] text-[#0e0e10] hover:bg-black/[0.05]">
                <Icon name="plus" size={12} /> New branch from here
              </button>
              <div className="flex-1" />
              <button className="flex items-center gap-1.5 px-2 py-1 rounded text-[12px] text-[#6b6b6b] hover:text-[var(--accent)] hover:bg-black/[0.05]">
                <Icon name="merge" size={12} /> Merge…
              </button>
            </div>
          </div>
        </>
      )}
    </div>
  );
}

function FilterButton() {
  const [open, setOpen] = useState(false);
  const [from, setFrom] = useState("2026-01-01");
  const [to, setTo] = useState("2026-05-09");
  const [contentTypes, setContentTypes] = useState(new Set(["doc", "chat", "code", "email", "image", "spreadsheet", "pdf"]));
  const [categories, setCategories] = useState(new Set(["knowhow", "correspondence", "invoice", "spec", "decision", "research", "meeting"]));
  const [authorOnlyMe, setAuthorOnlyMe] = useState(false);
  const [pinnedOnly, setPinnedOnly] = useState(false);
  const [minRelevance, setMinRelevance] = useState(0);

  const TYPES = [
    { id: "doc", label: "Documents", icon: "file" },
    { id: "chat", label: "Conversations", icon: "message" },
    { id: "code", label: "Code", icon: "code" },
    { id: "email", label: "Email", icon: "mail" },
    { id: "image", label: "Images", icon: "image" },
    { id: "spreadsheet", label: "Spreadsheets", icon: "grid" },
    { id: "pdf", label: "PDFs", icon: "pdf" },
  ];
  const CATS = [
    { id: "knowhow", label: "Know-how", color: "#6b7280" },
    { id: "correspondence", label: "Correspondence", color: "#3b5cf6" },
    { id: "invoice", label: "Invoice / Finance", color: "#0e7c66" },
    { id: "spec", label: "Spec / Story", color: "#8b5cf6" },
    { id: "decision", label: "Decision", color: "#d97706" },
    { id: "research", label: "Research / Citation", color: "#b6892e" },
    { id: "meeting", label: "Meeting notes", color: "#ec4899" },
  ];

  const toggle = (set, setSet, id) => {
    const ns = new Set(set);
    ns.has(id) ? ns.delete(id) : ns.add(id);
    setSet(ns);
  };

  const activeCount = (TYPES.length - contentTypes.size) + (CATS.length - categories.size) + (authorOnlyMe ? 1 : 0) + (pinnedOnly ? 1 : 0) + (minRelevance > 0 ? 1 : 0);
  const hasFilters = activeCount > 0;

  const setRange = (preset) => {
    const today = new Date("2026-05-09");
    const fmt = d => d.toISOString().slice(0, 10);
    const t = new Date(today);
    if (preset === "7d") t.setDate(t.getDate() - 7);
    else if (preset === "30d") t.setDate(t.getDate() - 30);
    else if (preset === "90d") t.setDate(t.getDate() - 90);
    else if (preset === "ytd") { t.setMonth(0); t.setDate(1); }
    else if (preset === "all") { setFrom(""); setTo(""); return; }
    setFrom(fmt(t)); setTo(fmt(today));
  };

  return (
    <div className="relative">
      <button onClick={() => setOpen(o => !o)}
        className={`relative p-1.5 rounded-md hover:bg-black/[0.04] hover:text-[#0e0e10] ${hasFilters ? "text-[var(--accent)] bg-[var(--accent)]/[0.08]" : "text-[#5a5a5a]"}`}
        title="Filter retrieval">
        <Icon name="filter-funnel" size={16} />
        {hasFilters && (
          <span className="absolute -top-0.5 -right-0.5 min-w-[14px] h-[14px] px-1 rounded-full bg-[var(--accent)] text-white text-[9.5px] font-semibold flex items-center justify-center">{activeCount}</span>
        )}
      </button>
      {open && (
        <>
          <div className="fixed inset-0 z-20" onClick={() => setOpen(false)} />
          <div className="absolute left-0 bottom-full mb-1.5 w-[440px] bg-white rounded-xl border border-[#ece7da] shadow-2xl z-30 overflow-hidden">
            <div className="px-3.5 py-2.5 border-b border-[#ece7da] flex items-center gap-2">
              <Icon name="filter-funnel" size={14} className="text-[var(--accent)]" />
              <div className="text-[12.5px] font-semibold text-[#0e0e10]">Filter retrieval</div>
              <span className="text-[11px] text-[#9aa0a6]">Hard filter — applied across all pinned context</span>
              <div className="flex-1" />
              {hasFilters && (
                <button onClick={() => { setContentTypes(new Set(TYPES.map(t => t.id))); setCategories(new Set(CATS.map(c => c.id))); setAuthorOnlyMe(false); setPinnedOnly(false); setMinRelevance(0); setRange("all"); }}
                  className="text-[11px] text-[#6b6b6b] hover:text-[var(--accent)]">Reset</button>
              )}
            </div>
            <div className="max-h-[420px] overflow-y-auto">
              {/* Time range */}
              <div className="px-3.5 py-3 border-b border-[#f0ebdd]">
                <div className="flex items-center justify-between mb-1.5">
                  <div className="text-[11px] font-semibold tracking-[0.06em] text-[#6b6b6b] uppercase">Time range</div>
                  <div className="flex items-center gap-1">
                    {[["7d","7d"],["30d","30d"],["90d","90d"],["ytd","YTD"],["all","All"]].map(([k,l]) => (
                      <button key={k} onClick={() => setRange(k)} className="text-[11px] px-1.5 py-0.5 rounded text-[#6b6b6b] hover:bg-black/[0.05] hover:text-[#0e0e10]">{l}</button>
                    ))}
                  </div>
                </div>
                <div className="flex items-center gap-2">
                  <input type="date" value={from} onChange={e => setFrom(e.target.value)} className="flex-1 px-2 py-1 rounded border border-[#ece7da] text-[12px] outline-none focus:border-[var(--accent)]" />
                  <span className="text-[11px] text-[#9aa0a6]">→</span>
                  <input type="date" value={to} onChange={e => setTo(e.target.value)} className="flex-1 px-2 py-1 rounded border border-[#ece7da] text-[12px] outline-none focus:border-[var(--accent)]" />
                </div>
              </div>
              {/* Content type */}
              <div className="px-3.5 py-3 border-b border-[#f0ebdd]">
                <div className="text-[11px] font-semibold tracking-[0.06em] text-[#6b6b6b] uppercase mb-1.5">Content type</div>
                <div className="flex flex-wrap gap-1.5">
                  {TYPES.map(t => {
                    const on = contentTypes.has(t.id);
                    return (
                      <button key={t.id} onClick={() => toggle(contentTypes, setContentTypes, t.id)}
                        className={`flex items-center gap-1.5 px-2 py-1 rounded-md text-[11.5px] border ${on ? "bg-white border-[#0e0e10] text-[#0e0e10]" : "bg-[#faf8f3] border-[#ece7da] text-[#9aa0a6]"}`}>
                        <Icon name={t.icon} size={12} />
                        {t.label}
                        {on && <Icon name="check" size={11} className="text-[var(--accent)]" />}
                      </button>
                    );
                  })}
                </div>
              </div>
              {/* Content category */}
              <div className="px-3.5 py-3 border-b border-[#f0ebdd]">
                <div className="text-[11px] font-semibold tracking-[0.06em] text-[#6b6b6b] uppercase mb-1.5">Content category</div>
                <div className="flex flex-wrap gap-1.5">
                  {CATS.map(c => {
                    const on = categories.has(c.id);
                    return (
                      <button key={c.id} onClick={() => toggle(categories, setCategories, c.id)}
                        className={`flex items-center gap-1.5 px-2 py-1 rounded-md text-[11.5px] border ${on ? "bg-white border-[#0e0e10] text-[#0e0e10]" : "bg-[#faf8f3] border-[#ece7da] text-[#9aa0a6]"}`}>
                        <span className="w-2 h-2 rounded-full" style={{ background: on ? c.color : "#cbcbcb" }} />
                        {c.label}
                      </button>
                    );
                  })}
                </div>
              </div>
              {/* More */}
              <div className="px-3.5 py-3">
                <div className="text-[11px] font-semibold tracking-[0.06em] text-[#6b6b6b] uppercase mb-2">More</div>
                <label className="flex items-center gap-2 py-1 cursor-pointer">
                  <input type="checkbox" checked={authorOnlyMe} onChange={e => setAuthorOnlyMe(e.target.checked)} className="accent-[var(--accent)]" />
                  <span className="text-[12.5px] text-[#1a1a1a]">Authored by me</span>
                  <span className="text-[11px] text-[#9aa0a6]">simon@contem.ch</span>
                </label>
                <label className="flex items-center gap-2 py-1 cursor-pointer">
                  <input type="checkbox" checked={pinnedOnly} onChange={e => setPinnedOnly(e.target.checked)} className="accent-[var(--accent)]" />
                  <span className="text-[12.5px] text-[#1a1a1a]">Pinned / starred only</span>
                </label>
                <div className="py-1.5">
                  <div className="flex items-center justify-between mb-1">
                    <span className="text-[12.5px] text-[#1a1a1a]">Minimum relevance</span>
                    <span className="text-[11px] text-[#6b6b6b] tabular-nums">{minRelevance > 0 ? `${(minRelevance * 100).toFixed(0)}%` : "off"}</span>
                  </div>
                  <input type="range" min="0" max="0.9" step="0.05" value={minRelevance} onChange={e => setMinRelevance(parseFloat(e.target.value))} className="w-full accent-[var(--accent)]" />
                </div>
              </div>
            </div>
            <div className="border-t border-[#ece7da] px-3.5 py-2 flex items-center bg-[#faf8f3] text-[11.5px] text-[#6b6b6b]">
              <Icon name="info" size={12} className="mr-1.5" />
              <span>Filter is <span className="font-semibold text-[#0e0e10]">hard</span> — items not matching are excluded from retrieval.</span>
              <div className="flex-1" />
              <button onClick={() => setOpen(false)} className="text-[12px] px-2.5 py-1 rounded bg-[var(--accent)] text-white hover:brightness-110">Apply</button>
            </div>
          </div>
        </>
      )}
    </div>
  );
}

function ContextChip({ chip, onRemove, onToggleRestricted, linkedNext }) {
  const map = {
    skill: { icon: "wand", color: "#8b5cf6", bg: "#f3eefe", border: "#e3d6fb" },
    entity: { icon: "user", color: "#b6892e", bg: "#fdf3d6", border: "#f0dca0" },
    agent: { icon: "robot", color: "#0e7c66", bg: "#e6f6f0", border: "#c4ead8" },
    file: { icon: "code", color: "#1f3df6", bg: "#eef0ff", border: "#d1d8ff" },
    story: { icon: "story", color: "#3b5cf6", bg: "#eef0ff", border: "#d1d8ff" },
    branch: { icon: "branch", color: "#0e7c66", bg: "#e6f6f0", border: "#c4ead8" },
    component: { icon: "hierarchy", color: "#a05a00", bg: "#fbf0d4", border: "#ecdbb0" },
  };
  const s = map[chip.kind] || map.skill;
  return (
    <span className="inline-flex items-center gap-1.5 pl-1.5 pr-1 py-0.5 rounded-md border text-[12px]" style={{ background: s.bg, borderColor: s.border, color: "#0e0e10" }}>
      <Icon name={s.icon} size={12} style={{ color: s.color }} />
      <span className={chip.kind === "branch" ? "font-mono text-[11.5px]" : ""}>{chip.label}</span>
      {chip.kind === "entity" && (
        <button onClick={onToggleRestricted}
          className={`ml-0.5 px-1.5 py-[1px] rounded text-[9.5px] font-semibold tracking-wider uppercase ${chip.restricted ? "bg-[#0e0e10] text-white" : "bg-white border border-[#e6e1d4] text-[#6b6b6b]"}`}
          title="Restrict context to this entity">
          {chip.restricted ? "Restricted" : "Broad"}
        </button>
      )}
      {linkedNext && (
        <span className="ml-0.5 inline-flex items-center text-[#0e7c66]" title={`Linked to ${linkedNext}`}>
          <Icon name="link" size={11} />
        </span>
      )}
      <button onClick={onRemove} className="p-0.5 rounded hover:bg-black/[0.06] text-[#6b6b6b]"><Icon name="x" size={11} /></button>
    </span>
  );
}

function MegaDropdown({ onClose, tab, setTab, contextChips, setContextChips }) {
  const tabs = [
    { id: "entities", label: "Entities", icon: "entity" },
    { id: "components", label: "Components", icon: "hierarchy" },
    { id: "sources", label: "Sources", icon: "source" },
    { id: "skills", label: "Skills", icon: "wand" },
    { id: "models", label: "Models", icon: "model" },
  ];
  const componentsList = [
    { id: "comp-chat-core",      label: "Chat Core",              sub: "Domain · 108 files · complex" },
    { id: "comp-composer",       label: "Composer & Attachments", sub: "Domain · 64 files · moderate" },
    { id: "comp-context-graph",  label: "Context Graph",          sub: "Domain · 47 files · complex" },
    { id: "comp-streaming",      label: "Streaming & Tools",      sub: "Domain · 53 files · complex" },
    { id: "comp-auth",           label: "Auth & RBAC",            sub: "Domain · 31 files · moderate" },
    { id: "comp-knowledge",      label: "Knowledge Indexer",      sub: "Domain · 72 files · moderate" },
    { id: "comp-plugin-host",    label: "Plugin Host",            sub: "Domain · 22 files · simple" },
    { id: "comp-telemetry",      label: "Telemetry",              sub: "Domain · 18 files · simple" },
  ];
  const entityGroups = [
    { id: "people", label: "People", icon: "users", tint: "#d6a648", items: [
      { id: "felix-ent", label: "Felix Haas", sub: "Composer · 9 chats" },
      { id: "anita-ent", label: "Anita Roth", sub: "Virtualization · 5 chats" },
      { id: "lukas-ent", label: "Lukas Berg", sub: "State & storage · 4 chats" },
      { id: "maya-ent", label: "Maya Chen", sub: "PM · 7 chats" },
    ]},
    { id: "stories", label: "Jira Stories", icon: "jira", tint: "#3b5cf6", items: [
      { id: "j1", label: "Allow attachments in Teklens.AI", sub: "TEK-140 · 4 chats" },
      { id: "j2", label: "Multi-tenant role permissions", sub: "TEK-152 · 2 chats" },
      { id: "j3", label: "Inline comments on canvas", sub: "TEK-166 · 1 chat" },
      { id: "j4", label: "Audit log redesign", sub: "TEK-178 · 6 chats" },
      { id: "j5", label: "Webhook retry policy", sub: "TEK-184 · 0 chats" },
    ]},
    { id: "components", label: "Code Components", icon: "code", tint: "#10b981", items: [
      { id: "co1", label: "ChatThread", sub: "React · 14 files" },
      { id: "co2", label: "useEntityIndex", sub: "Hook · 3 files" },
      { id: "co3", label: "RAGRetriever", sub: "Service · 8 files" },
    ]},
    { id: "knowhow", label: "Know-how", icon: "hash", tint: "#6b7280", items: [
      { id: "kh1", label: "Coding guidelines", sub: "Doc · 12 sections" },
      { id: "kh2", label: "Streaming architecture", sub: "Wiki · 4 pages" },
      { id: "kh3", label: "Testing paradigms", sub: "Concept · 6 notes" },
    ]},
  ];
  const sourceGroups = [
    { id: "folders", label: "Folders", icon: "folder", tint: "#d6a648", items: [
      { id: "src-notes", label: "Notes / Simon", sub: "Local · 142 files" },
      { id: "src-pm", label: "PM Drafts", sub: "Local · 38 files" },
    ]},
    { id: "repos", label: "Repositories", icon: "repo", tint: "#10b981", items: [
      { id: "src-teklens-mobile", label: "contem/teklens-ai-mobile", sub: "GitHub · main" },
      { id: "src-teklens", label: "contem/teklens-ai", sub: "GitHub · main" },
    ]},
    { id: "confluence", label: "Confluence Spaces", icon: "confluence", tint: "#1c4f9c", items: [
      { id: "src-eng", label: "Engineering Wiki", sub: "256 pages" },
      { id: "src-product", label: "Product Specs", sub: "94 pages" },
    ]},
    { id: "jira-projects", label: "Jira Projects", icon: "jira", tint: "#3b5cf6", items: [
      { id: "src-tek", label: "Teklens (TEK)", sub: "1,204 issues" },
      { id: "src-tek-mobile", label: "Teklens Mobile (TKM)", sub: "318 issues" },
    ]},
  ];
  const skillsList = [
    { id: "ca", label: "Component Analyzer", sub: "Static analysis on code components" },
    { id: "sd", label: "Story Drafter", sub: "Generates Jira stories from notes" },
    { id: "dle", label: "Domain Logic Extractor", sub: "Pulls invariants from code" },
    { id: "ra", label: "Risk Analyzer", sub: "PM toolkit" },
    { id: "rd", label: "RAG Drafter", sub: "Cite-grounded long-form" },
    { id: "search-local", label: "search_local_files", sub: "Built-in" },
  ];
  const models = [
    { id: "gemini-3pro", label: "Gemini 3.1 Pro", provider: "gemini", sub: "Reasoning · 2M ctx" },
    { id: "gemini-3flash", label: "Gemini 3.1 Flash", provider: "gemini", sub: "Fast · 1M ctx" },
    { id: "gemini-2flashlite", label: "Gemini 2.5 Flash Lite", provider: "gemini", sub: "Cheap · 200K ctx" },
    { id: "claude-sonnet", label: "Claude Sonnet 4.6", provider: "anthropic", sub: "Reasoning · 200K ctx" },
    { id: "claude-haiku", label: "Claude Haiku 4.5", provider: "anthropic", sub: "Fast · 200K ctx" },
    { id: "gpt5", label: "GPT-5.1 Reasoning", provider: "openai", sub: "Top-tier · 400K ctx" },
    { id: "gpt5mini", label: "GPT-5.1 Mini", provider: "openai", sub: "Balanced" },
    { id: "grok4", label: "Grok 4 Heavy", provider: "grok", sub: "xAI · 256K ctx" },
    { id: "glm51", label: "GLM 5.1", provider: "cerebras", sub: "Cerebras · ultra-fast" },
    { id: "anon-sonnet", label: "Anonymized Claude Sonnet 4.6", provider: "anthropic", anonymous: true, sub: "Routed via Teklens proxy · zero retention" },
    { id: "anon-gpt5", label: "Anonymized GPT-5.1", provider: "openai", anonymous: true, sub: "Routed via Teklens proxy · zero retention" },
    { id: "anon-gemini", label: "Anonymized Gemini 3.1 Pro", provider: "gemini", anonymous: true, sub: "Routed via Teklens proxy · zero retention" },
  ];
  const add = (item, kind, extra = {}) => {
    if (contextChips.find(c => c.id === item.id)) return;
    setContextChips(cs => [...cs, { id: item.id, kind, label: item.label, restricted: kind === "entity" || kind === "source", ...extra }]);
  };
  return (
    <>
      <div className="fixed inset-0 z-20" onClick={onClose} />
      <div className="absolute left-0 bottom-full mb-1.5 w-[560px] bg-white rounded-xl border border-[#ece7da] shadow-2xl z-30 overflow-hidden">
        <div className="flex items-center gap-1 px-2 pt-2 pb-2.5 border-b border-[#ece7da]">
          {tabs.map(t => (
            <button key={t.id} onClick={() => setTab(t.id)}
              className={`flex items-center gap-1.5 px-2.5 py-1.5 text-[12.5px] rounded-md ${tab === t.id ? "text-[#0e0e10] bg-[#f4f1ea]" : "text-[#6b6b6b] hover:text-[#0e0e10]"}`}>
              <Icon name={t.icon} size={13} /> {t.label}
            </button>
          ))}
          <div className="flex-1" />
          <div className="flex items-center gap-1.5 px-3 py-1.5 mx-1 rounded-md border border-[#ece7da] text-[12.5px] text-[#6b6b6b] flex-1 max-w-[260px] focus-within:border-[var(--accent)]">
            <Icon name="search" size={13} />
            <input type="text" placeholder="Search…" className="bg-transparent outline-none flex-1 text-[#0e0e10] placeholder:text-[#9aa0a6]" />
          </div>
        </div>
        <div className="max-h-[360px] overflow-y-auto">
          {tab === "entities" && entityGroups.map(g => (
            <Group key={g.id} g={g} contextChips={contextChips} onAdd={(item) => add(item, "entity", { groupTint: g.tint, groupIcon: g.icon })} />
          ))}
          {tab === "sources" && sourceGroups.map(g => (
            <Group key={g.id} g={g} contextChips={contextChips} onAdd={(item) => add(item, "source", { groupTint: g.tint, groupIcon: g.icon })} />
          ))}
          {tab === "skills" && (
            <div className="py-1">
              {skillsList.map(s => <Row key={s.id} icon="wand" tint="#8b5cf6" item={s} picked={!!contextChips.find(c => c.id === s.id)} onClick={() => add(s, "skill")} />)}
            </div>
          )}
          {tab === "components" && (
            <div className="py-1">
              {componentsList.map(c => <Row key={c.id} icon="hierarchy" tint="#a05a00" item={c} picked={!!contextChips.find(x => x.id === c.id)} onClick={() => add(c, "component")} />)}
            </div>
          )}
          {tab === "models" && (
            <div className="py-1">
              {models.map(m => (
                <button key={m.id} onClick={() => add(m, "model", { provider: m.provider, anonymous: m.anonymous })}
                  className="w-full flex items-center gap-3 px-3 py-2 hover:bg-[#faf7f0] text-left">
                  <div className="relative w-7 h-7 rounded-md bg-[#f4f1ea] flex items-center justify-center text-[#3a3a3a]">
                    <Icon name={m.provider} size={14} />
                    {m.anonymous && (
                      <span className="absolute -bottom-1 -right-1 w-3.5 h-3.5 rounded-full bg-[#0e0e10] text-white flex items-center justify-center" title="Anonymized via proxy">
                        <Icon name="shield" size={9} stroke={2} />
                      </span>
                    )}
                  </div>
                  <div className="flex-1 min-w-0">
                    <div className="flex items-center gap-1.5">
                      <div className="text-[13px] text-[#0e0e10] truncate">{m.label}</div>
                      {m.anonymous && <span className="text-[9.5px] font-semibold tracking-wider uppercase px-1.5 py-[1px] rounded bg-[#0e0e10] text-white">Anon</span>}
                    </div>
                    <div className="text-[11.5px] text-[#9aa0a6] truncate">{m.sub}</div>
                  </div>
                  {!!contextChips.find(c => c.id === m.id) ? <span className="text-[11px] text-[var(--accent)] font-medium">Pinned</span> : <Icon name="plus" size={14} className="text-[#9aa0a6]" />}
                </button>
              ))}
            </div>
          )}
        </div>
        <div className="border-t border-[#ece7da] px-3 py-2 flex items-center gap-2 bg-[#faf8f3] text-[11.5px] text-[#6b6b6b]">
          <Icon name="pin" size={12} />
          {tab === "sources" ? <span>Pin a source to <span className="font-semibold text-[#0e0e10]">restrict retrieval</span> to it.</span> :
           tab === "models" ? <span>Anonymized models route through the Teklens proxy with zero retention.</span> :
           tab === "skills" ? <span>Skills extend the assistant — one skill per plugin.</span> :
           tab === "components" ? <span>Pin a component to scope analysis to its <span className="font-semibold text-[#0e0e10]">domain</span>. Chat-scope only.</span> :
           <span>Pin entities to scope retrieval. Toggle <span className="font-semibold text-[#0e0e10]">Restricted</span> per chip.</span>}
        </div>
      </div>
    </>
  );
}

function Group({ g, contextChips, onAdd }) {
  return (
    <div className="border-b border-[#f0ebdd] last:border-b-0">
      <div className="flex items-center gap-1.5 px-3 py-1.5 bg-[#faf8f3]">
        <Icon name={g.icon} size={12} style={{ color: g.tint }} />
        <span className="text-[11px] font-semibold tracking-[0.06em] text-[#6b6b6b] uppercase">{g.label}</span>
        <span className="text-[11px] text-[#bbb]">· {g.items.length}</span>
      </div>
      {g.items.map(it => (
        <Row key={it.id} icon={g.icon} tint={g.tint} item={it} picked={!!contextChips.find(c => c.id === it.id)} onClick={() => onAdd(it)} />
      ))}
    </div>
  );
}

function Row({ icon, tint, item, picked, onClick }) {
  return (
    <button onClick={onClick} className="w-full flex items-center gap-3 px-3 py-2 hover:bg-[#faf7f0] text-left">
      <div className="w-7 h-7 rounded-md bg-[#f4f1ea] flex items-center justify-center" style={{ color: tint }}>
        <Icon name={icon} size={14} />
      </div>
      <div className="flex-1 min-w-0">
        <div className="text-[13px] text-[#0e0e10] truncate">{item.label}</div>
        <div className="text-[11.5px] text-[#9aa0a6] truncate">{item.sub}</div>
      </div>
      {picked ? <span className="text-[11px] text-[var(--accent)] font-medium">Pinned</span> : <Icon name="plus" size={14} className="text-[#9aa0a6]" />}
    </button>
  );
}
