import { useState, useEffect, useRef } from "react";
import { BASE, deleteRepo, fetchMcpStatus, fetchMcpPrompt } from "../api";

function ContextualTip() {
  const [open, setOpen] = useState(false);
  return (
    <div className="ctip">
      <button className="ctip-trigger" onClick={() => setOpen(o => !o)}>
        <svg width="10" height="10" viewBox="0 0 16 16" fill="currentColor" style={{ opacity: 0.5, flexShrink: 0 }}>
          <path d="M8 1a7 7 0 1 0 0 14A7 7 0 0 0 8 1zm-.5 4.5h1v1.5h-1zm0 3h1v4h-1z"/>
        </svg>
        <span>Improve search quality</span>
        <svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ marginLeft: "auto", opacity: 0.4, transition: "transform 0.2s", transform: open ? "rotate(180deg)" : "none" }}>
          <path d="m4 6 4 4 4-4"/>
        </svg>
      </button>
      {open && (
        <p className="ctip-body">
          Hit <span className="quality-tip-key">⟳</span> on any repo to re-index with <strong>contextual retrieval</strong> — the AI prepends a description to each key chunk before embedding. Searches, diagrams, and the semantic map all improve.
        </p>
      )}
    </div>
  );
}

function SessionItem({ sess, onLoad, onDelete, onRename, isActive }) {
  const [confirming, setConfirming] = useState(false);
  const [editing, setEditing]       = useState(false);
  const [editVal, setEditVal]       = useState(sess.title);
  const inputRef = useRef(null);

  // Focus the input when entering edit mode
  useEffect(() => {
    if (editing && inputRef.current) inputRef.current.focus();
  }, [editing]);

  function startEdit(e) {
    e.stopPropagation();
    setEditVal(sess.title);
    setEditing(true);
  }

  function commitEdit() {
    const trimmed = editVal.trim();
    if (trimmed && trimmed !== sess.title) onRename(sess.id, trimmed);
    setEditing(false);
  }

  function handleEditKey(e) {
    if (e.key === "Enter") { e.preventDefault(); commitEdit(); }
    if (e.key === "Escape") { setEditing(false); }
  }

  return (
    <div className={`session-item${isActive ? " active" : ""}`}>
      {editing ? (
        <input
          ref={inputRef}
          className="session-title-input"
          value={editVal}
          onChange={e => setEditVal(e.target.value)}
          onBlur={commitEdit}
          onKeyDown={handleEditKey}
          onClick={e => e.stopPropagation()}
          maxLength={80}
          aria-label="Edit session title"
        />
      ) : (
        <button className="session-btn" onClick={() => onLoad(sess)} onDoubleClick={startEdit} title={`${sess.title}\n(double-click to rename)`}>
          <span className="session-title">{sess.title}</span>
          <span style={{ display: "flex", alignItems: "center", gap: 4, flexShrink: 0 }}>
            {sess.agentMode && <span className="session-mode-badge" title="Agent mode session">✦</span>}
            <span className="session-time">{timeAgo(sess.timestamp)}</span>
          </span>
        </button>
      )}
      {confirming ? (
        <span className="session-confirm">
          <button className="session-confirm-yes" onClick={() => { onDelete(sess.id); setConfirming(false); }}>Remove</button>
          <button className="session-confirm-no"  onClick={() => setConfirming(false)}>Cancel</button>
        </span>
      ) : (
        <button className="session-delete" onClick={() => setConfirming(true)} title="Delete session" aria-label="Delete session">×</button>
      )}
    </div>
  );
}

function timeAgo(iso) {
  const diff = Date.now() - new Date(iso).getTime();
  const m = Math.floor(diff / 60000);
  if (m < 1)  return "just now";
  if (m < 60) return `${m}m ago`;
  const h = Math.floor(m / 60);
  if (h < 24) return `${h}h ago`;
  return `${Math.floor(h / 24)}d ago`;
}

// Staleness thresholds: warn if index is older than 3 days, stale if > 7 days.
function stalenessLevel(isoTimestamp) {
  if (!isoTimestamp) return null;
  const days = (Date.now() - new Date(isoTimestamp).getTime()) / (1000 * 60 * 60 * 24);
  if (days < 3) return null;       // fresh — no indicator
  if (days < 7) return "warn";     // getting old
  return "stale";                  // definitely stale
}

export default function Sidebar({ repos, reposLoading, activeRepo, onSelectRepo, onReposChange, mode, onModeChange, agentMode, onAgentModeChange, sessions, currentSessionId, onLoadSession, onDeleteSession, onRenameSession, isOpen, onClose, collapsed, onToggleCollapse, onGenerateReadme, isLanding = false }) {
  const [url, setUrl]                   = useState("");
  const [status, setStatus]             = useState(null); // {type, text}
  const [loading, setLoading]           = useState(false);
  const [mcpInfo, setMcpInfo]           = useState(null); // MCP server status
  const [mcpOpen, setMcpOpen]           = useState(false); // expand/collapse panel
  const [mcpExpandedKey, setMcpExpandedKey] = useState(null); // "tool:name" | "res:uri" | "prompt:name"
  const [mcpPromptPreview, setMcpPromptPreview] = useState({}); // name → text (fetched lazily)
  const [confirming, setConfirming]     = useState(null); // slug being confirmed for delete
  const [ingestProgress, setIngestProgress] = useState([]); // [{step, detail, done}]
  const [isIngesting, setIsIngesting]   = useState(false);
  const [reindexing, setReindexing]     = useState(null);  // slug currently re-indexing
  const [reindexDone, setReindexDone]   = useState({});    // slug → bool (just finished)
  const [reindexPct,  setReindexPct]    = useState({});    // slug → 0-100 progress %
  const [sessionSearch, setSessionSearch] = useState("");  // filter text for sessions list

  // Load MCP status once on mount
  useEffect(() => {
    fetchMcpStatus().then(setMcpInfo).catch(() => setMcpInfo({ connected: false }));
  }, []);

  // Landing hero → Sidebar bridge. The hero lives in the main pane and
  // has no reference to this component's state, so it asks us to ingest
  // by dispatching a window-level event. We pre-fill the URL, expand the
  // sidebar (so the user can watch the progress steps), and submit.
  useEffect(() => {
    function onExternalIngest(e) {
      const repo = e.detail?.repo;
      if (!repo) return;
      setUrl(repo);
      // defer to next tick so the controlled input has flushed
      setTimeout(() => {
        document.querySelector('.ingest-form')?.requestSubmit();
      }, 0);
    }
    window.addEventListener("cartographer:ingest", onExternalIngest);
    return () => window.removeEventListener("cartographer:ingest", onExternalIngest);
  }, []);

  function handleIngest(e) {
    e.preventDefault();
    if (!url.trim() || isIngesting) return;

    setIsIngesting(true);
    setIngestProgress([]);
    setStatus(null);

    // Connect to the SSE stream — the server pushes step events as it progresses
    // through fetching → filtering → chunking → embedding → storing → done.
    // EventSource handles reconnection automatically on network blips, so we
    // explicitly close it once we receive "done" or "error" to prevent that.
    const streamUrl = `${BASE}/ingest/stream?repo=${encodeURIComponent(url.trim())}`;
    const es = new EventSource(streamUrl);

    es.onmessage = (e) => {
      const event = JSON.parse(e.data);

      setIngestProgress(prev => {
        // Mark all previous steps as completed, then append the new active step.
        const updated = prev.map(s => ({ ...s, done: true }));
        return [...updated, { step: event.step, detail: event.detail, done: false }];
      });

      if (event.step === "done" || event.step === "error") {
        // The final step was appended as active (done: false). Mark it done now —
        // no subsequent event will arrive to flip it, so we do it explicitly.
        setIngestProgress(prev => prev.map(s => ({ ...s, done: true })));
        es.close();
        setIsIngesting(false);
        if (event.step === "done") {
          // Extract owner/repo slug from the URL the user typed.
          // Handles both "github.com/owner/repo" and "https://github.com/owner/repo".
          const match = url.match(/github\.com\/([^/]+\/[^/]+)/);
          if (match && onSelectRepo) onSelectRepo(match[1]);
          setUrl("");
          onReposChange();
          // Collapse the progress list after 3s so the card returns to normal size
          setTimeout(() => setIngestProgress([]), 3000);
        }
      }
    };

    es.onerror = () => {
      es.close();
      setIsIngesting(false);
      setIngestProgress(prev => [
        ...prev,
        { step: "error", detail: "Connection failed — is the backend running?", done: false },
      ]);
    };
  }

  async function handleDelete(e, slug) {
    e.stopPropagation();
    try {
      await deleteRepo(slug);
      if (activeRepo === slug) onSelectRepo(null);
      onReposChange();
    } catch (err) {
      setStatus({ type: "error", text: err.message });
    }
  }

  function handleReindex(e, slug) {
    e.stopPropagation();
    if (reindexing) return;
    setReindexing(slug);
    setReindexDone(prev => ({ ...prev, [slug]: false }));
    setReindexPct(prev => ({ ...prev, [slug]: 5 }));

    // Map ingestion steps to approximate % complete so the bar fills meaningfully.
    // "contextualizing" is dynamic — we compute it from the "X / Y" in the detail.
    const STEP_PCT = { fetching: 10, filtering: 22, chunking: 38, embedding: 80, storing: 92, done: 100 };

    // Use EventSource (GET SSE) instead of a POST fetch so the connection never
    // times out — large repos take several minutes to re-embed. The backend sends
    // keepalive pings every 15s to prevent proxy idle-disconnect.
    //
    // IMPORTANT: EventSource auto-reconnects when the server closes the stream.
    // We must call es.close() as soon as we receive any terminal event (done/error)
    // to prevent it from replaying the force=true re-index a second time.
    const es = new EventSource(`${BASE}/ingest/stream?repo=${encodeURIComponent(`https://github.com/${slug}`)}&force=true`);
    let completed = false; // true once "done" event received
    let closed    = false; // guard against double-close / double-onerror

    const closeEs = () => { if (!closed) { closed = true; es.close(); } };

    es.onmessage = (ev) => {
      const event = JSON.parse(ev.data);
      let pct = STEP_PCT[event.step] ?? null;

      // Contextualizing fires many times with "X / Y" in the detail.
      // Map it to 38–78% range so the bar visibly advances during this long phase.
      if (event.step === "contextualizing" && event.detail) {
        const m = event.detail.match(/(\d+)\s*\/\s*(\d+)/);
        if (m) {
          const [done, total] = [parseInt(m[1]), parseInt(m[2])];
          pct = Math.round(38 + (done / total) * 40);
        } else {
          pct = 40; // initial "contextualizing" event before first batch
        }
      }

      if (pct !== null) setReindexPct(prev => ({ ...prev, [slug]: pct }));

      if (event.step === "done") {
        completed = true;
        closeEs();
        setReindexing(null);
        setReindexDone(prev => ({ ...prev, [slug]: true }));
        onReposChange();
        setTimeout(() => {
          setReindexDone(prev => { const n = {...prev}; delete n[slug]; return n; });
          setReindexPct(prev => { const n = {...prev}; delete n[slug]; return n; });
        }, 8000); // matches reindex-done-fade animation duration
      } else if (event.step === "error") {
        closeEs();
        setReindexing(null);
        setReindexPct(prev => { const n = {...prev}; delete n[slug]; return n; });
        setStatus({ type: "error", text: `Re-index failed: ${event.detail}` });
      }
    };

    es.onerror = () => {
      if (closed) return; // already handled — prevent double-fire
      closeEs();
      setReindexing(null);
      setReindexPct(prev => { const n = {...prev}; delete n[slug]; return n; });
      onReposChange();
      // Defer the error display by one event-loop tick.
      // When the server sends "done" and immediately closes the stream, the browser
      // can queue onerror (connection-close) BEFORE delivering the final onmessage.
      // The setTimeout(0) lets any pending onmessage callbacks flush first, so
      // `completed` is already true by the time we check it here.
      setTimeout(() => {
        if (!completed) {
          setStatus({ type: "error", text: "Re-index may have completed — connection dropped at the end. Check the chunk count." });
        }
      }, 0);
    };
  }

  const SEARCH_MODE_TITLES = {
    hybrid: "Combines text matching + semantic similarity (recommended)",
    semantic: "Finds conceptually similar code",
    keyword: "Exact identifier matching",
  };

  // ── Collapsed rail ────────────────────────────────────────────────────────
  // When collapsed, show a slim 52px icon strip with key counts + expand button.
  // Same pattern as rag-research-copilot: two separate JSX trees, no CSS trickery.
  if (collapsed) {
    return (
      <div className="sidebar sidebar-collapsed">
        {/* Brand icon */}
        <svg width="28" height="28" viewBox="0 0 24 24" fill="none" style={{ margin: '12px 0 4px' }}>
          <path d="M12 2 L14.5 7 L12 12 L9.5 7 Z" fill="var(--accent)"/>
          <path d="M12 22 L13.5 17 L12 12 L10.5 17 Z" fill="var(--accent)" opacity="0.28"/>
          <path d="M22 12 L17 10.5 L12 12 L17 13.5 Z" fill="var(--accent)" opacity="0.28"/>
          <path d="M2 12 L7 10.5 L12 12 L7 13.5 Z" fill="var(--accent)" opacity="0.28"/>
          <circle cx="12" cy="12" r="1.4" fill="var(--accent)"/>
        </svg>

        {/* Repo count */}
        {repos.length > 0 && (
          <div className="sidebar-collapsed-item" title={`${repos.length} repo${repos.length !== 1 ? 's' : ''} indexed`}>
            <svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor" style={{ opacity: 0.5 }}>
              <path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Z"/>
            </svg>
            <span className="sidebar-collapsed-badge">{repos.length}</span>
          </div>
        )}

        {/* Session count */}
        {sessions && sessions.length > 0 && (
          <div className="sidebar-collapsed-item" title={`${sessions.length} saved chat${sessions.length !== 1 ? 's' : ''}`}>
            <svg width="13" height="13" viewBox="0 0 16 16" fill="currentColor" style={{ opacity: 0.5 }}>
              <path d="M1 2.75C1 1.784 1.784 1 2.75 1h10.5c.966 0 1.75.784 1.75 1.75v7.5A1.75 1.75 0 0 1 13.25 12H9.06l-2.573 2.573A1.457 1.457 0 0 1 4 13.543V12H2.75A1.75 1.75 0 0 1 1 10.25Z"/>
            </svg>
            <span className="sidebar-collapsed-badge">{sessions.length}</span>
          </div>
        )}

        {/* Expand button — pinned to bottom */}
        <button
          className="sidebar-collapsed-expand"
          onClick={onToggleCollapse}
          title="Expand sidebar"
          aria-label="Expand sidebar"
        >
          <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <path d="m6 4 4 4-4 4"/>
          </svg>
        </button>
      </div>
    );
  }

  return (
    <div className={`sidebar ${isOpen ? "open" : ""}`}>
      {/* ── Scrollable top section ── */}
      <div className="sidebar-scroll">

      {/* ── Brand ── */}
      <div className="sidebar-brand">
        {/* Icon container — Raycast-style rounded square with gradient + compass inside */}
        <div className="sidebar-brand-icon">
          <svg width="22" height="22" viewBox="0 0 24 24" fill="none">
            {/* Subtle glow behind compass — same as favicon */}
            <circle cx="12" cy="12" r="10" fill="rgba(91,143,249,0.10)"/>
            {/* N — dominant, full accent blue */}
            <path d="M12 2 L14.5 7 L12 12 L9.5 7 Z" fill="#5B8FF9"/>
            {/* S/E/W — dim */}
            <path d="M12 22 L13.5 17 L12 12 L10.5 17 Z" fill="#5B8FF9" opacity="0.28"/>
            <path d="M22 12 L17 10.5 L12 12 L17 13.5 Z" fill="#5B8FF9" opacity="0.28"/>
            <path d="M2 12 L7 10.5 L12 12 L7 13.5 Z" fill="#5B8FF9" opacity="0.28"/>
            {/* Center pivot — white for contrast */}
            <circle cx="12" cy="12" r="1.6" fill="white"/>
          </svg>
        </div>
        <div style={{ flex: 1 }}>
          <div className="sidebar-brand-name">Cartographer</div>
        </div>
        <button
          className="sidebar-collapse-btn"
          onClick={onToggleCollapse}
          title="Collapse sidebar"
          aria-label="Collapse sidebar"
        >
          <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
            <path d="m10 4-4 4 4 4"/>
          </svg>
        </button>
      </div>

      {/* ── Ingest ── hidden on landing (the hero owns this primary action) */}
      {!isLanding && (
      <div className="sidebar-section">
        <div className="section-label">Add Repository</div>
        <div className="ingest-card">
        <form className="ingest-form" onSubmit={handleIngest}>
          <input
            type="text"
            placeholder="github.com/owner/repo"
            value={url}
            onChange={(e) => setUrl(e.target.value)}
            disabled={isIngesting}
          />
          <button className="btn" type="submit" disabled={isIngesting || !url.trim()} title="Index repository">
            {isIngesting
              ? <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round"><path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83"/></svg>
              : <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M3 8h10M9 4l4 4-4 4"/></svg>
            }
          </button>
        </form>
        {/* Curated repos — quick-start for new users */}
        <div style={{ marginTop: 10 }}>
          <div style={{ marginBottom: 5, color: "var(--faint)", fontSize: 10, textTransform: "uppercase", letterSpacing: "0.06em", fontWeight: 600 }}>Try these</div>
          <div className="try-repo-chips">
            {[
              { slug: "karpathy/nanoGPT", label: "GPT from scratch" },
              { slug: "karpathy/micrograd", label: "autograd engine" },
              { slug: "langchain-ai/langchain", label: "LLM framework" },
            ].map(({ slug, label }) => (
              <button
                key={slug}
                className="try-repo-chip"
                onClick={() => setUrl(`github.com/${slug}`)}
                title={label}
              >
                <svg width="10" height="10" viewBox="0 0 16 16" fill="currentColor" style={{ opacity: 0.6, flexShrink: 0 }}>
                  <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
                </svg>
                {slug.split("/")[1]}
              </button>
            ))}
          </div>
        </div>
        {status && (
          <div className={`status-bar ${status.type}`} style={{ marginTop: 8 }}>
            {status.text}
          </div>
        )}
        {ingestProgress.length > 0 && (
          <div className="ingest-progress">
            {ingestProgress.map((p, i) => (
              <div
                key={i}
                className={`ingest-step ${p.done ? "done" : "active"} ${p.step === "error" ? "error" : ""}`}
              >
                <span className="ingest-step-icon">
                  {p.step === "error" ? (
                    /* X circle */
                    <svg width="11" height="11" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
                      <path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zm3.03 10.97L10.03 12 8 9.97 5.97 12l-1-1.03L7 8.97 5 6.97l1-1 2 2 2-2 1 1-2 2z"/>
                    </svg>
                  ) : p.done ? (
                    /* Check circle */
                    <svg width="11" height="11" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
                      <path d="M8 0a8 8 0 1 0 0 16A8 8 0 0 0 8 0zm3.78 6.22-4.5 4.5a.75.75 0 0 1-1.06 0l-2-2a.75.75 0 1 1 1.06-1.06l1.47 1.47 3.97-3.97a.75.75 0 1 1 1.06 1.06z"/>
                    </svg>
                  ) : (
                    /* Spinner dots — three dots for "in progress" */
                    <svg width="11" height="11" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true">
                      <circle cx="2" cy="8" r="1.5"/><circle cx="8" cy="8" r="1.5"/><circle cx="14" cy="8" r="1.5"/>
                    </svg>
                  )}
                </span>
                <span className="ingest-step-detail">{p.detail}</span>
              </div>
            ))}
          </div>
        )}
        </div>{/* end ingest-card */}
      </div>
      )}

      {/* ── Query mode (RAG vs Agent) ── hidden on landing (no chat yet) */}
      {!isLanding && (
      <div className="sidebar-section">
        <div className="section-label">Query Mode</div>
        <div className="mode-pills">
          <button
            className={`pill ${!agentMode ? "active" : ""}`}
            onClick={() => onAgentModeChange(false)}
            aria-pressed={!agentMode}
          >RAG</button>
          <button
            className={`pill pill--agent ${agentMode ? "active" : ""}`}
            onClick={() => onAgentModeChange(true)}
            aria-pressed={agentMode}
          >
            <span className="pill-mark" aria-hidden="true">✦</span>
            Agent
          </button>
        </div>
        <p className="mode-description">
          {agentMode
            ? "Searches → reads → searches again. Slower but thorough."
            : "Retrieves code once, streams an answer. Fast."}
        </p>
      </div>
      )}

      {/* ── Search mode (only visible in RAG mode, and not on landing) ── */}
      {!isLanding && !agentMode && (
        <div className="sidebar-section">
          <div className="section-label">Search Mode</div>
          <div className="mode-pills">
            {["hybrid", "semantic", "keyword"].map((m) => (
              <button
                key={m}
                className={`pill ${mode === m ? "active" : ""}`}
                onClick={() => onModeChange(m)}
                aria-pressed={mode === m}
              >{m}</button>
            ))}
          </div>
          <p className="mode-description">
            {mode === "hybrid"   && "Text + semantic combined. Best for most questions."}
            {mode === "semantic" && "Finds conceptually similar code, even without exact terms."}
            {mode === "keyword"  && "Exact identifier matching. Best for function or class names."}
          </p>
        </div>
      )}

      {/* ── Repos ── */}
      <div className="sidebar-section">
        <div className="section-label">Indexed Repos ({reposLoading ? "…" : repos.length})</div>
        {reposLoading ? (
          // Skeleton while the first fetch is in flight — backend can take a moment on cold start
          <div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 4 }}>
            {[1, 2].map(i => (
              <div key={i} style={{
                height: 34, borderRadius: "var(--radius-sm)",
                background: "var(--surface-3)",
                animation: "pulse 1.4s ease-in-out infinite",
                animationDelay: `${i * 0.15}s`,
              }} />
            ))}
          </div>
        ) : repos.length === 0 ? (
          <p style={{ fontSize: 13, color: "var(--muted)", lineHeight: 1.5 }}>
            No repos indexed yet. Add one above.
          </p>
        ) : (
          <div className="repo-list">
            {repos.map((r) => {
              const staleness = stalenessLevel(r.indexed_at);
              const isReindexingThis = reindexing === r.slug;
              const justDone = reindexDone[r.slug];
              const pct = reindexPct[r.slug] ?? null;
              return (
                <div
                  key={r.slug}
                  className={`repo-item ${activeRepo === r.slug ? "active" : ""}`}
                  onClick={() => onSelectRepo(activeRepo === r.slug ? null : r.slug)}
                  style={{ position: "relative", overflow: "hidden" }}
                >
                  <div className="repo-item-main">
                    {/* GitHub mark — reinforces these are GitHub repos without taking space */}
                    <svg width="11" height="11" viewBox="0 0 16 16" fill="currentColor" style={{ opacity: 0.3, flexShrink: 0 }}>
                      <path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"/>
                    </svg>
                    {/* Split slug: owner dimmed + repo name prominent — scanability trick from Linear */}
                    <span className="repo-slug">
                      <span className="repo-owner">{r.slug.split("/")[0]}/</span>{r.slug.split("/")[1]}
                    </span>
                    <div className="repo-item-meta">
                      {/* Staleness indicator — shown when index is > 3 days old */}
                      {staleness && !justDone && (
                        <span className={`repo-staleness repo-staleness--${staleness}`} title={`Indexed ${timeAgo(r.indexed_at)}`}>
                          {staleness === "warn" ? "~old" : "stale"}
                        </span>
                      )}
                      {justDone && (
                        <span className="repo-staleness repo-staleness--fresh">updated</span>
                      )}
                      {r.contextual_at && (
                        <span className="repo-contextual" title={`Contextual retrieval applied — re-indexed ${timeAgo(r.contextual_at)}`} aria-label="Contextual retrieval applied">
                          <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
                            {/* Sparkle: one large 4-point star + two tiny companions.
                                Reads unambiguously as "AI enhanced" at any size. */}
                            <path d="M12 3v4m0 10v4M3 12h4m10 0h4M6.7 6.7l2.8 2.8m5 5 2.8 2.8M6.7 17.3l2.8-2.8m5-5 2.8-2.8"/>
                          </svg>
                        </span>
                      )}
                      <span className="repo-count" title={`${r.chunks} indexed code chunks`}>{r.chunks}</span>
                    </div>
                  </div>
                  <div className="repo-item-actions">
                    {/* README generator — subtle hover-only action */}
                    {onGenerateReadme && (
                      <button
                        className="repo-readme-btn"
                        onClick={(e) => { e.stopPropagation(); onGenerateReadme(r.slug); }}
                        title="Generate README"
                        aria-label={`Generate README for ${r.slug}`}
                      >
                        <svg width="11" height="11" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" aria-hidden="true">
                          <rect x="2" y="1" width="9" height="13" rx="1"/>
                          <path d="M5 5h4M5 7h4M5 9h2"/>
                          <path d="M9 1v3h3"/>
                        </svg>
                      </button>
                    )}
                    {/* Re-index button — one click re-ingests from scratch */}
                    <button
                      className={`repo-reindex${isReindexingThis ? " spinning" : ""}${justDone ? " done-glow" : ""}`}
                      onClick={(e) => handleReindex(e, r.slug)}
                      disabled={!!reindexing}
                      title={isReindexingThis ? "Re-indexing…" : "Re-index with contextual retrieval — adds AI-generated descriptions to key chunks before embedding, improving search precision"}
                      aria-label={`Re-index ${r.slug}`}
                    >
                      ⟳
                    </button>
                    {confirming === r.slug ? (
                      <span style={{ display: "flex", gap: 2, alignItems: "center", flexShrink: 0 }}>
                        <button
                          className="repo-confirm-yes"
                          onClick={(e) => { e.stopPropagation(); handleDelete(e, r.slug); setConfirming(null); }}
                        >Delete</button>
                        <button
                          className="repo-confirm-no"
                          onClick={(e) => { e.stopPropagation(); setConfirming(null); }}
                        >Cancel</button>
                      </span>
                    ) : (
                      <button
                        className="repo-delete"
                        onClick={(e) => { e.stopPropagation(); setConfirming(r.slug); }}
                        title="Remove from index"
                        aria-label={`Remove ${r.slug} from index`}
                      >×</button>
                    )}
                  </div>
                  {/* Progress bar — shown while re-indexing, then holds at 100% and
                      glows for 8s after completion before fading out */}
                  {(pct !== null || justDone) && (
                    <div className="repo-reindex-progress">
                      <div
                        className={`repo-reindex-progress-bar${justDone ? " done" : ""}`}
                        style={{ width: justDone ? "100%" : `${pct}%` }}
                      />
                    </div>
                  )}
                </div>
              );
            })}
          </div>
        )}
        {!isLanding && repos.length > 0 && <ContextualTip />}
      </div>

      {/* ── Recent chats ── */}
      {sessions && sessions.length > 0 && (
        <div className="sidebar-section">
          <div className="section-label">Recent chats</div>
          {/* Session search — visible when there are enough sessions to warrant filtering */}
          {sessions.length >= 3 && (
            <input
              className="session-search"
              type="text"
              placeholder="Search chats…"
              value={sessionSearch}
              onChange={e => setSessionSearch(e.target.value)}
              aria-label="Search sessions"
            />
          )}
          <div className="session-list">
            {sessions
              .filter(sess => !sessionSearch || sess.title.toLowerCase().includes(sessionSearch.toLowerCase()))
              .map(sess => (
                <SessionItem
                  key={sess.id}
                  sess={sess}
                  isActive={sess.id === currentSessionId}
                  onLoad={onLoadSession}
                  onDelete={onDeleteSession}
                  onRename={onRenameSession}
                />
              ))
            }
            {sessionSearch && sessions.filter(s => s.title.toLowerCase().includes(sessionSearch.toLowerCase())).length === 0 && (
              <div style={{ fontSize: 12, color: "var(--muted)", padding: "6px 0" }}>No chats match "{sessionSearch}"</div>
            )}
          </div>
        </div>
      )}

      </div>{/* end sidebar-scroll */}

      {/* ── MCP Server Status — pinned at bottom, does not scroll with sidebar ── */}
      <div className="mcp-panel">
        <button
          className="mcp-panel-header"
          onClick={() => setMcpOpen(o => !o)}
          aria-expanded={mcpOpen}
          aria-controls="mcp-panel-body"
        >
          <span className={`mcp-dot ${mcpInfo?.connected ? "connected" : "disconnected"}`} />
          <span className="mcp-panel-title">MCP Server</span>
          {mcpInfo?.connected && (
            <span className="mcp-counts">
              {mcpInfo.tools.length}T · {mcpInfo.resources.length}R · {mcpInfo.prompts.length}P
            </span>
          )}
          {/* Panel expands UPWARD from the bottom. Closed state points up
              (where the panel will appear); open state points down (where
              it will collapse back to). The down-chevron SVG is the base —
              rotate it when closed so the caret matches the action. */}
          <svg
            className="mcp-chevron"
            width="10" height="10" viewBox="0 0 16 16"
            fill="none" stroke="currentColor" strokeWidth="2"
            strokeLinecap="round" strokeLinejoin="round"
            style={{ transform: mcpOpen ? "none" : "rotate(180deg)", transition: "transform 0.2s" }}
            aria-hidden="true"
          >
            <path d="m4 6 4 4 4-4"/>
          </svg>
        </button>

        {mcpOpen && mcpInfo && (
          <div id="mcp-panel-body" className="mcp-panel-body">
            {!mcpInfo.connected ? (
              <p className="mcp-error">Not connected — is the backend running?</p>
            ) : (
              <>
                {/* Primer — one line explaining what this panel exposes.
                    Turns a debug list into a piece of the product story. */}
                <p className="mcp-primer">
                  Live from the backend — every capability the agent uses to reason over your code.
                </p>
                {mcpInfo.tools.length > 0 && (
                  <div className="mcp-section">
                    <div className="mcp-section-label">
                      <span>Tools</span>
                      <span className="mcp-section-count">{mcpInfo.tools.length}</span>
                    </div>
                    {mcpInfo.tools.map(t => {
                      const key = `tool:${t.name}`;
                      const expanded = mcpExpandedKey === key;
                      return (
                        <div key={t.name} className={`mcp-row${expanded ? " is-open" : ""}`}>
                          <button
                            type="button"
                            className="mcp-item"
                            onClick={() => setMcpExpandedKey(expanded ? null : key)}
                            aria-expanded={expanded}
                          >
                            <span className="mcp-kind mcp-kind-tool" aria-hidden="true">
                              <svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M6 3 3 6l3 3M10 13l3-3-3-3M9 4 7 12"/></svg>
                            </span>
                            <span className="mcp-item-content">
                              <span className="mcp-item-name">{t.name}</span>
                              {t.description && <span className="mcp-item-desc">{t.description}</span>}
                            </span>
                            <svg className="mcp-item-chevron" width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="m4 6 4 4 4-4"/></svg>
                          </button>
                          {expanded && t.description && (
                            <div className="mcp-detail">
                              <p className="mcp-detail-desc">{t.description}</p>
                            </div>
                          )}
                        </div>
                      );
                    })}
                  </div>
                )}
                {mcpInfo.resources.length > 0 && (
                  <div className="mcp-section">
                    <div className="mcp-section-label">
                      <span>Resources</span>
                      <span className="mcp-section-count">{mcpInfo.resources.length}</span>
                    </div>
                    {mcpInfo.resources.map(r => {
                      const key = `res:${r.uri}`;
                      const expanded = mcpExpandedKey === key;
                      return (
                        <div key={r.uri} className={`mcp-row${expanded ? " is-open" : ""}`}>
                          <button
                            type="button"
                            className="mcp-item"
                            onClick={() => setMcpExpandedKey(expanded ? null : key)}
                            aria-expanded={expanded}
                          >
                            <span className="mcp-kind mcp-kind-resource" aria-hidden="true">
                              <svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M8 2C4.7 2 2 3.3 2 5v6c0 1.7 2.7 3 6 3s6-1.3 6-3V5c0-1.7-2.7-3-6-3Z"/><path d="M2 5c0 1.7 2.7 3 6 3s6-1.3 6-3M2 8c0 1.7 2.7 3 6 3s6-1.3 6-3"/></svg>
                            </span>
                            <span className="mcp-item-content">
                              <span className="mcp-item-name">{r.name || r.uri.split("://").pop()}</span>
                              <span className="mcp-item-desc mcp-uri">{r.uri}</span>
                            </span>
                            <svg className="mcp-item-chevron" width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="m4 6 4 4 4-4"/></svg>
                          </button>
                          {expanded && (
                            <div className="mcp-detail">
                              <p className="mcp-detail-desc">
                                {r.description || "Read-only resource exposed over MCP."}
                              </p>
                            </div>
                          )}
                        </div>
                      );
                    })}
                  </div>
                )}
                {mcpInfo.prompts.length > 0 && (
                  <div className="mcp-section">
                    <div className="mcp-section-label">
                      <span>Prompts</span>
                      <span className="mcp-section-count">{mcpInfo.prompts.length}</span>
                    </div>
                    {mcpInfo.prompts.map(p => {
                      const key = `prompt:${p.name}`;
                      const expanded = mcpExpandedKey === key;
                      const preview = mcpPromptPreview[p.name];
                      const args = p.arguments || [];
                      const hasRequiredArgs = args.some(a => a.required);
                      return (
                        <div key={p.name} className={`mcp-row${expanded ? " is-open" : ""}`}>
                          <button
                            type="button"
                            className="mcp-item"
                            onClick={() => setMcpExpandedKey(expanded ? null : key)}
                            aria-expanded={expanded}
                          >
                            <span className="mcp-kind mcp-kind-prompt" aria-hidden="true">
                              <svg width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M3 3h10M3 6h10M3 9h7M3 12h4"/></svg>
                            </span>
                            <span className="mcp-item-content">
                              <span className="mcp-item-name">/{p.name}</span>
                              {p.description && <span className="mcp-item-desc">{p.description}</span>}
                            </span>
                            <svg className="mcp-item-chevron" width="10" height="10" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="m4 6 4 4 4-4"/></svg>
                          </button>
                          {expanded && (
                            <div className="mcp-detail">
                              {p.description && <p className="mcp-detail-desc">{p.description}</p>}
                              {args.length > 0 && (
                                <div className="mcp-sig">
                                  <div className="mcp-sig-label">Arguments</div>
                                  {args.map(a => (
                                    <div key={a.name} className="mcp-sig-arg">
                                      <span className="mcp-sig-name">{a.name}</span>
                                      {a.required && <span className="mcp-sig-req">required</span>}
                                      {a.description && <span className="mcp-sig-desc">{a.description}</span>}
                                    </div>
                                  ))}
                                </div>
                              )}
                              {preview && (
                                <pre className="mcp-detail-preview">{preview}</pre>
                              )}
                              {!hasRequiredArgs && !preview && (
                                <button
                                  type="button"
                                  className="mcp-detail-action"
                                  onClick={async () => {
                                    try {
                                      const { text } = await fetchMcpPrompt(p.name, {});
                                      setMcpPromptPreview(prev => ({ ...prev, [p.name]: text }));
                                    } catch (err) {
                                      setMcpPromptPreview(prev => ({ ...prev, [p.name]: `Error: ${err.message}` }));
                                    }
                                  }}
                                >
                                  Preview expanded prompt
                                </button>
                              )}
                              {hasRequiredArgs && !preview && (
                                <p className="mcp-detail-hint">
                                  Invoke from chat: type <code>/{p.name}</code> in the message box.
                                </p>
                              )}
                            </div>
                          )}
                        </div>
                      );
                    })}
                  </div>
                )}
              </>
            )}
          </div>
        )}
      </div>
    </div>
  );
}
