// Candidate detail — Notion-page style notes, no live discussion clutter.
// Used standalone (full-screen artboard) and as a pop-up from the kanban.

const { useState: useStateC, useEffect: useEffectC, useRef: useRefC } = React;

function StageFlow({ currentStageId }) {
  const flow = STAGES.filter(s => !["backburner","rejected","not_interested","declined"].includes(s.id));
  const idx = flow.findIndex(s => s.id === currentStageId);
  return (
    <div className="ats-stage-flow">
      {flow.map((s, i) => {
        const cls = i < idx ? " is-done" : i === idx ? " is-current" : "";
        return (
          <React.Fragment key={s.id}>
            <span className={"ats-stage-flow__step" + cls}>
              <span className="ats-col__dot" style={{ background: i <= idx ? s.accent : "rgba(29,49,64,0.18)" }} />
              {s.label}
            </span>
            {i < flow.length - 1 && <span className="ats-stage-flow__sep">→</span>}
          </React.Fragment>
        );
      })}
    </div>
  );
}

function SubmitterCard({ c }) {
  const d = c._detail || {};
  const why = d.submitter_why;
  const relocate = d.relocation_willing;
  const inPerson = d.in_person_willing;
  if (!why && !relocate && !inPerson) return null;

  const labelFor = (v) => {
    if (!v) return null;
    const norm = String(v).toLowerCase();
    if (norm === "yes") return "Yes";
    if (norm === "no") return "No";
    if (norm === "open" || norm === "maybe") return "Open to it";
    return v;
  };

  const submitterName = d.referrer || c.referrer || d.submitted_by || "External submission";

  return (
    <div className="ats-ai-card" style={{
      background: "linear-gradient(180deg, rgba(196, 162, 101, 0.10), rgba(196, 162, 101, 0.04))",
      borderColor: "rgba(196, 162, 101, 0.30)",
      marginBottom: 12,
    }}>
      <div className="ats-ai-card__head">
        <span className="ats-ai-card__sparkle" style={{ background: "#C4A265" }}>↗</span>
        <span className="ats-ai-card__title">From {submitterName}</span>
      </div>
      <div className="ats-ai-card__body">
        {why && (
          <div style={{ marginBottom: (relocate || inPerson) ? 10 : 0 }}>
            <div style={{ fontSize: 11, color: "var(--fg-3)", marginBottom: 4, textTransform: "uppercase", letterSpacing: "0.04em" }}>
              Why interested
            </div>
            <div style={{ fontSize: 13.5, lineHeight: 1.6, color: "var(--fg-1)", whiteSpace: "pre-wrap" }}>{why}</div>
          </div>
        )}
        {(relocate || inPerson) && (
          <div style={{ display: "flex", gap: 18, flexWrap: "wrap" }}>
            {relocate && (
              <div>
                <div style={{ fontSize: 11, color: "var(--fg-3)", marginBottom: 2, textTransform: "uppercase", letterSpacing: "0.04em" }}>
                  Relocating to Seattle
                </div>
                <div style={{ fontSize: 13, color: "var(--fg-1)" }}>{labelFor(relocate)}</div>
              </div>
            )}
            {inPerson && (
              <div>
                <div style={{ fontSize: 11, color: "var(--fg-3)", marginBottom: 2, textTransform: "uppercase", letterSpacing: "0.04em" }}>
                  5 days in-person
                </div>
                <div style={{ fontSize: 13, color: "var(--fg-1)" }}>{labelFor(inPerson)}</div>
              </div>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

function AISummaryCard({ c }) {
  const jobTitle = JOBS.find(j => j.id === c.jobId)?.title;
  const [generating, setGenerating] = useStateC(false);
  const [brief, setBrief] = useStateC(null);

  const displayBrief = brief || c._detail?.ai_summary;

  const [briefError, setBriefError] = useStateC(null);
  const regenerate = async () => {
    setGenerating(true);
    setBriefError(null);
    try {
      const resp = await fetch(`${API_BASE_URL}/candidates/${c.id}/brief`, {
        method: "POST",
        headers: { Authorization: `Bearer ${localStorage.getItem("ats_token")}` },
      });
      if (!resp.ok) {
        setGenerating(false);
        setBriefError("Failed to regenerate brief.");
        return;
      }
      const poll = setInterval(async () => {
        const detail = await fetchCandidateDetail(c.id);
        if (detail?.ai_summary && detail.ai_summary !== displayBrief) {
          setBrief(detail.ai_summary);
          setGenerating(false);
          clearInterval(poll);
        }
      }, 3000);
      setTimeout(() => { clearInterval(poll); setGenerating(false); }, 30000);
    } catch { setGenerating(false); setBriefError("Failed to regenerate brief."); }
  };

  return (
    <div className="ats-ai-card">
      <div className="ats-ai-card__head">
        <span className="ats-ai-card__sparkle">N</span>
        <span className="ats-ai-card__title">Candidate brief</span>
        <button
          className="ats-ai-card__regen"
          onClick={regenerate}
          disabled={generating}
          title="Regenerate brief"
        >{generating ? "Generating..." : "Regenerate"}</button>
      </div>
      <div className="ats-ai-card__body">
        {briefError && <p style={{ color: "#c0392b", fontSize: 12, marginBottom: 8 }}>{briefError}</p>}
        {generating
          ? <p style={{ color: "var(--fg-3)", fontStyle: "italic" }}>Generating candidate brief...</p>
          : displayBrief
            ? <div dangerouslySetInnerHTML={{ __html: marked.parse(displayBrief, { breaks: true }) }} />
            : c._detail?.resume_summary
              ? <div dangerouslySetInnerHTML={{ __html: marked.parse(c._detail.resume_summary, { breaks: true }) }} />
              : <p><em>{c.first}</em>{jobTitle ? <> — candidate for <b>{jobTitle}</b></> : ""}.</p>
        }
      </div>
    </div>
  );
}

// === Per-candidate live note + score store ===========================
// Module-level reactive store. Components subscribe via useAtsCandidateStore(c.id)
// and re-render when posts mutate the data.
const __atsStore = {
  // candidateId -> { ratings: [...], notes: [...], interviews: [...] }
  data: new Map(),
  listeners: new Set(),
  get(cid) {
    if (!this.data.has(cid)) this.data.set(cid, { ratings: [], notes: [], interviews: [] });
    return this.data.get(cid);
  },
  setInterviews(cid, interviews) {
    this.get(cid).interviews = interviews;
    this._emit();
  },
  addRating(cid, rating) {
    this.get(cid).ratings.push(rating);
    this._emit();
  },
  addNote(cid, note) {
    this.get(cid).notes.unshift(note); // newest on top
    this._emit();
  },
  subscribe(fn) { this.listeners.add(fn); return () => this.listeners.delete(fn); },
  _emit() { this.listeners.forEach(fn => fn()); },
};
function useAtsCandidateStore(cid) {
  const [, force] = useStateC(0);
  useEffectC(() => __atsStore.subscribe(() => force(n => n + 1)), []);
  return __atsStore.get(cid);
}

// Combined ratings = API interviews + any posts for this candidate.
function ratingsFor(cid) {
  return [...__atsStore.get(cid).interviews, ...__atsStore.get(cid).ratings];
}

// ---- Radar chart primitives (kept for potential future use) ----
function consensusLabel(spread) {
  if (spread == null) return { label: "—", tone: "neutral" };
  if (spread <= 0.5) return { label: "Tight", tone: "good" };
  if (spread <= 1.0) return { label: "Aligned", tone: "good" };
  if (spread <= 1.5) return { label: "Mixed", tone: "warn" };
  return { label: "Split", tone: "bad" };
}

// Lay out N axes evenly around a circle, starting at the top (12 o'clock).
function axisPoints(n, cx, cy, r) {
  const out = [];
  for (let i = 0; i < n; i++) {
    const a = -Math.PI / 2 + (i * 2 * Math.PI) / n;
    out.push({ x: cx + Math.cos(a) * r, y: cy + Math.sin(a) * r, angle: a });
  }
  return out;
}
// Returns the polygon points string for an array of values (0..5) along axes.
function polyPoints(values, axes, cx, cy, rMax) {
  return values.map((v, i) => {
    const r = (Math.max(0, Math.min(5, v)) / 5) * rMax;
    const a = axes[i].angle;
    return `${cx + Math.cos(a) * r},${cy + Math.sin(a) * r}`;
  }).join(" ");
}


// ====== Notion-style notes ======
// Each note is a page-level block: author row + clean body + optional reactions.
// No chrome, no borders — the page IS the document.

function NotionNote({ n, onDelete }) {
  const [confirmDelete, setConfirmDelete] = useStateC(false);
  return (
    <div className="ats-note">
      <div className="ats-note__head">
        <InitialDisc initials={n.author.initials} color={n.author.color} size={22} />
        <span className="ats-note__author">{n.author.name}</span>
        {n.author.role && <span className="ats-note__role">{n.author.role}</span>}
        <span className="ats-note__time">{n.time}</span>
        {n.pinned && <span className="ats-note__pin">⚲</span>}
        {onDelete && !confirmDelete && (
          <button className="ats-note__delete" title="Delete note" onClick={() => setConfirmDelete(true)}>&#x2715;</button>
        )}
        {onDelete && confirmDelete && (
          <span style={{ marginLeft: 8, fontSize: 11, color: "var(--fg-3)" }}>
            Delete?{" "}
            <button style={{ background: "none", border: "none", cursor: "pointer", color: "#B4553D", fontWeight: 600, fontSize: 11 }} onClick={() => { onDelete(n.id); setConfirmDelete(false); }}>Yes</button>
            {" / "}
            <button style={{ background: "none", border: "none", cursor: "pointer", color: "var(--fg-3)", fontSize: 11 }} onClick={() => setConfirmDelete(false)}>No</button>
          </span>
        )}
      </div>
      <div className="ats-note__body">{n.body}</div>
      {n.attached && (
        <div className="ats-note__attach">
          <span>📎</span>
          {typeof n.attached === "string" ? (
            <span>{n.attached}</span>
          ) : n.attached.url ? (
            <a href={n.attached.url} target="_blank" rel="noopener noreferrer" style={{ color: "var(--fg-1)" }}>{n.attached.name}</a>
          ) : (
            <span>{n.attached.name}</span>
          )}
        </div>
      )}
      {n.reactions && (
        <div className="ats-note__reactions">
          {n.reactions.map((r, i) => (
            <button key={i} className={"ats-reaction" + (r.mine ? " is-mine" : "")}
                    onClick={(e) => { e.currentTarget.classList.toggle("is-mine"); }}
                    title={r.mine ? "Remove your reaction" : "React"}>
              {r.emoji} <span style={{ fontFamily: "var(--font-mono)" }}>{r.count}</span>
            </button>
          ))}
          <button className="ats-reaction ats-reaction--add" title="Add reaction"
                  onClick={(e) => {
                    const emojis = ["👍","🎉","💯","👀","🙌","🔥"];
                    const pick = emojis[Math.floor(Math.random() * emojis.length)];
                    const btn = document.createElement("button");
                    btn.className = "ats-reaction is-mine";
                    btn.innerHTML = `${pick} <span style="font-family: var(--font-mono)">1</span>`;
                    e.currentTarget.parentNode.insertBefore(btn, e.currentTarget);
                  }}>＋</button>
          <button className="ats-note__reply"
                  onClick={(e) => {
                    const note = e.currentTarget.closest(".ats-note") || e.currentTarget.parentElement;
                    const existing = note.querySelector(".ats-note__reply-box");
                    if (existing) { existing.remove(); return; }
                    const box = document.createElement("div");
                    box.className = "ats-note__reply-box";
                    box.style.cssText = "margin-top:8px;padding:6px 10px;background:var(--bg-2);border-radius:6px;font-size:12px;color:var(--fg-3)";
                    box.textContent = "Reply field — coming soon";
                    note.appendChild(box);
                  }}>Reply</button>
        </div>
      )}
    </div>
  );
}

function NotesComposer({ c }) {
  const viewer = useViewer();
  const [expanded, setExpanded] = useStateC(false);
  const [posted, setPosted] = useStateC(false);
  const [saveError, setSaveError] = useStateC(null);
  const [saving, setSaving] = useStateC(false);
  const EMPTY = { rawNotes: "", tldr: "", summary: "" };
  const [vals, setVals] = useStateC(EMPTY);
  const [noteStage, setNoteStage] = useStateC(c.stage || "sourced");
  const [attachedFile, setAttachedFile] = useStateC(null);
  const fileInputRef = React.useRef(null);
  const MAX_ATTACH = 25 * 1024 * 1024;
  const setField = (k) => (e) => setVals(prev => ({ ...prev, [k]: e.target.value }));
  // Auto-bullet: typing "- " (or "* ") at start of any line converts it to "• ".
  // Pressing Enter inside a bullet line continues the list; Enter on empty bullet exits.
  const applyBulletConvert = (k, ta, v) => {
    const caret = ta.selectionStart;
    if (caret < 2) return false;
    const last2 = v.slice(caret - 2, caret);
    if (last2 !== "- " && last2 !== "* ") return false;
    const lineStart = v.lastIndexOf("\n", caret - 3) + 1;
    const before = v.slice(lineStart, caret - 2);
    if (before.trim().length > 0) return false; // only at line start (allow leading whitespace)
    const next = v.slice(0, caret - 2) + before + "• " + v.slice(caret);
    ta.value = next;
    const pos = (lineStart + before.length) + 2;
    ta.setSelectionRange(pos, pos);
    setVals(prev => ({ ...prev, [k]: next }));
    return true;
  };
  const onKey = (k) => (e) => {
    if (e.key === "Escape") { e.stopPropagation(); setExpanded(false); return; }
    if (e.key === "Tab") {
      const ta = e.target;
      const v = ta.value;
      const caret = ta.selectionStart;
      const lineStart = v.lastIndexOf("\n", caret - 1) + 1;
      const lineEnd = v.indexOf("\n", caret);
      const line = v.slice(lineStart, lineEnd === -1 ? v.length : lineEnd);
      const m = line.match(/^(\s*)([-•*])\s/);
      if (m) {
        e.preventDefault();
        if (e.shiftKey) {
          const indent = m[1];
          if (indent.length >= 2) {
            const next = v.slice(0, lineStart) + line.slice(2) + v.slice(lineEnd === -1 ? v.length : lineEnd);
            ta.value = next;
            ta.setSelectionRange(Math.max(lineStart, caret - 2), Math.max(lineStart, caret - 2));
            setVals(prev => ({ ...prev, [k]: next }));
          }
        } else {
          const next = v.slice(0, lineStart) + "  " + line + v.slice(lineEnd === -1 ? v.length : lineEnd);
          ta.value = next;
          ta.setSelectionRange(caret + 2, caret + 2);
          setVals(prev => ({ ...prev, [k]: next }));
        }
        return;
      }
    }
    if (e.key === "Enter" && !e.shiftKey) {
      const ta = e.target;
      const v = ta.value;
      const caret = ta.selectionStart;
      const lineStart = v.lastIndexOf("\n", caret - 1) + 1;
      const line = v.slice(lineStart, caret);
      const m = line.match(/^(\s*)([-•*])\s+(.*)$/);
      if (m) {
        e.preventDefault();
        if (!m[3]) {
          const next = v.slice(0, lineStart) + v.slice(caret);
          ta.value = next;
          ta.setSelectionRange(lineStart, lineStart);
          setVals(prev => ({ ...prev, [k]: next }));
          return;
        }
        const bullet = m[2] === "*" ? "•" : m[2];
        const insert = "\n" + m[1] + bullet + " ";
        const next = v.slice(0, caret) + insert + v.slice(caret);
        ta.value = next;
        const pos = caret + insert.length;
        ta.setSelectionRange(pos, pos);
        setVals(prev => ({ ...prev, [k]: next }));
      }
    }
  };
  const onInput = (k) => (e) => {
    const ta = e.target;
    if (e.nativeEvent.inputType === "insertText" && e.nativeEvent.data === " ") {
      applyBulletConvert(k, ta, ta.value);
    }
  };
  const FIELDS = [
    { id: "rawNotes", icon: "brain",   label: "Raw notes",             hint: "Brain dump — anything worth capturing · use - for bullets", rows: 6 },
    { id: "tldr",     icon: "spark",   label: "TLDR",                  hint: "One sentence: your overall impression",                    rows: 1 },
    { id: "summary",  icon: "chat",    label: "Summary & justification", hint: "Expand on the TLDR — what drove your assessment",       rows: 4 },
  ];
  if (!expanded) {
    return (
      <>
        <button className="ats-notes-cta" onClick={() => setExpanded(true)}>
          <span className="ats-notes-cta__icon">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2"><path d="M12 5v14M5 12h14"/></svg>
          </span>
          <span className="ats-notes-cta__text">
            <b>Write an interview note</b>
            <span>Raw notes · TLDR · summary</span>
          </span>
          <span className="ats-notes-cta__kbd"><kbd>N</kbd></span>
        </button>
        {posted && (
          <div className="ats-toast" role="status">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.4"><path d="M5 12l5 5L20 7"/></svg>
            Note posted to timeline
          </div>
        )}
      </>
    );
  }
  return (
    <div className="ats-notes-compose ats-notes-compose--structured">
      <div className="ats-notes-compose__head">
        <InitialDisc initials={viewer.initials} color={viewer.color} size={22} />
        <span className="ats-note__author">{viewer.name}</span>
        <select
          value={noteStage}
          onChange={(e) => setNoteStage(e.target.value)}
          style={{
            fontSize: 11, padding: "2px 6px", borderRadius: 4,
            border: "1px solid var(--border-1)", background: "var(--bg-3)",
            color: "var(--fg-2)", cursor: "pointer", fontFamily: "var(--font-sans)",
          }}
          title="Interview stage"
        >
          {STAGES.filter(s => !["backburner","rejected","not_interested","declined"].includes(s.id)).map(s => (
            <option key={s.id} value={s.id}>{s.label}</option>
          ))}
        </select>
        <span className="ats-note__time">now</span>
        <span className="ats-notes-compose__head-spacer" />
        <span className="ats-notes-compose__hint">Tab to indent bullets · ⌘⏎ to post</span>
      </div>

      <div className="ats-notes-fields">
        {FIELDS.map(f => (
          <div key={f.id} className="ats-notes-field">
            <label className="ats-notes-field__label">
              <NoteIcon kind={f.icon} />
              <span className="ats-notes-field__title">{f.label}</span>
              <span className="ats-notes-field__hint">{f.hint}</span>
            </label>
            <textarea
              className="ats-notes-field__input"
              rows={f.rows}
              placeholder="…"
              value={vals[f.id]}
              onChange={setField(f.id)}
              onInput={onInput(f.id)}
              onKeyDown={onKey(f.id)}
            />
          </div>
        ))}

      </div>

      {attachedFile && (
        <div className="ats-note__attach" style={{ marginLeft: 0, marginTop: 4, marginBottom: 4 }}>
          <span>📎</span>
          <span style={{ flex: 1 }}>{attachedFile.name} <span style={{ color: "var(--fg-3)" }}>({Math.round(attachedFile.size / 1024)} KB)</span></span>
          <button
            type="button"
            title="Remove attachment"
            onClick={() => setAttachedFile(null)}
            style={{ background: "none", border: 0, cursor: "pointer", color: "var(--fg-3)", fontSize: 14, lineHeight: 1, padding: "0 4px" }}
          >×</button>
        </div>
      )}

      <div className="ats-notes-compose__footer">
        <div className="ats-notes-compose__tools">
          <button className="ats-composer__tool" title="Mention" type="button"
                  onClick={() => {
                    const ta = document.querySelector(".ats-notes-compose textarea");
                    if (ta) { ta.focus(); ta.value += "@"; ta.dispatchEvent(new Event("input", { bubbles: true })); }
                  }}>@</button>
          <input
            ref={fileInputRef}
            type="file"
            style={{ display: "none" }}
            onChange={(e) => {
              const f = e.target.files && e.target.files[0];
              e.target.value = "";
              if (!f) return;
              if (f.size > MAX_ATTACH) {
                setSaveError(`Attachment too large (max ${MAX_ATTACH / (1024 * 1024)}MB)`);
                return;
              }
              setSaveError(null);
              setAttachedFile(f);
            }}
          />
          <button className="ats-composer__tool" title="Attach file" type="button"
                  onClick={() => fileInputRef.current && fileInputRef.current.click()}>📎</button>
          <button className="ats-composer__tool" title="Template" type="button"
                  onClick={() => {
                    const ta = document.querySelector(".ats-notes-compose textarea");
                    if (ta) { ta.focus(); ta.value += "/"; ta.dispatchEvent(new Event("input", { bubbles: true })); }
                  }}>/</button>
        </div>
        <span className="ats-notes-compose__hint" style={{ flex: 1 }}>Saves to candidate timeline · visible to panel</span>
        <button className="ats-btn ats-btn--ghost" onClick={() => { setAttachedFile(null); setExpanded(false); }}>Cancel</button>
        <button
          className="ats-btn ats-btn--primary"
          style={{ padding: "6px 14px" }}
          disabled={saving}
          onClick={async () => {
            const filled = FIELDS.filter(f => vals[f.id].trim().length > 0);
            const feedbackText = filled.map(f => `## ${f.label}\n${vals[f.id].trim()}`).join("\n\n");
            setSaving(true);
            setSaveError(null);
            try {
              const resp = await fetch(`${API_BASE_URL}/candidates/${c.id}/interviews`, {
                method: "POST",
                headers: { "Content-Type": "application/json", Authorization: `Bearer ${localStorage.getItem("ats_token")}` },
                body: JSON.stringify({
                  feedback: feedbackText,
                  interviewer_name: viewer.name,
                  stage: noteStage,
                }),
              });
              if (!resp.ok) {
                const errBody = await resp.text().catch(() => "");
                throw new Error(errBody || `Server error (${resp.status})`);
              }
              const created = await resp.json().catch(() => null);
              let attachedName = null;
              if (attachedFile && created && created.id) {
                const fd = new FormData();
                fd.append("file", attachedFile);
                const upResp = await fetch(`${API_BASE_URL}/candidates/${c.id}/interviews/${created.id}/attachment`, {
                  method: "POST",
                  headers: { Authorization: `Bearer ${localStorage.getItem("ats_token")}` },
                  body: fd,
                });
                if (!upResp.ok) {
                  const upErr = await upResp.text().catch(() => "");
                  throw new Error(upErr || `Attachment upload failed (${upResp.status})`);
                }
                attachedName = attachedFile.name;
              }
              // Build the optimistic note with the SAME shape that NotesStream
              // produces from the server response — markdown-parsed body + the
              // {name, url} attachment object — so the post-submit render looks
              // identical to the post-refresh render. (Bug: previously the body
              // used custom section divs and attachment was a bare string, so
              // it appeared malformed until the page was refreshed.)
              const stageLabel = (STAGE_BY_ID[noteStage] || {}).label || noteStage;
              const body = (
                <div dangerouslySetInnerHTML={{ __html: marked.parse(feedbackText, { breaks: true }) }} />
              );
              const note = {
                id: created?.id || ("n_" + Date.now()),
                author: { name: viewer.name, initials: viewer.initials, color: viewer.color, role: stageLabel },
                time: "just now",
                body,
                canDelete: true,
                attached: attachedName && created?.id
                  ? { name: attachedName, url: `${API_BASE_URL}/candidates/${c.id}/interviews/${created.id}/attachment` }
                  : null,
              };
              __atsStore.addNote(c.id, note);
              setVals(EMPTY);
              setAttachedFile(null);
              setExpanded(false);
              setPosted(true);
              setTimeout(() => setPosted(false), 2400);
            } catch (err) {
              console.error("Failed to save interview note:", err);
              setSaveError("Failed to save note. Please try again.");
            } finally {
              setSaving(false);
            }
          }}
        >
          {saving ? "Saving…" : "Post note"}
        </button>
      </div>
      {saveError && <div style={{ color: "#c0392b", fontSize: "12px", padding: "6px 12px" }}>{saveError}</div>}
    </div>
  );
}

function NoteIcon({ kind }) {
  const common = { width: 14, height: 14, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 1.8, strokeLinecap: "round", strokeLinejoin: "round" };
  switch (kind) {
    case "brain":
      return <svg {...common}><path d="M9 4a3 3 0 0 0-3 3v.5A3 3 0 0 0 4 10v2a3 3 0 0 0 1.5 2.6V16a3 3 0 0 0 3 3h.5"/><path d="M15 4a3 3 0 0 1 3 3v.5a3 3 0 0 1 2 2.5v2a3 3 0 0 1-1.5 2.6V16a3 3 0 0 1-3 3H15"/><path d="M12 4v16"/></svg>;
    case "flag":
      return <svg {...common}><path d="M5 21V4"/><path d="M5 4h11l-2 4 2 4H5"/></svg>;
    case "spark":
      return <svg {...common}><path d="M12 3v4M12 17v4M3 12h4M17 12h4M5.6 5.6l2.8 2.8M15.6 15.6l2.8 2.8M5.6 18.4l2.8-2.8M15.6 8.4l2.8-2.8"/></svg>;
    case "users":
      return <svg {...common}><circle cx="9" cy="8" r="3"/><path d="M3 20c0-3.3 2.7-6 6-6s6 2.7 6 6"/><circle cx="17" cy="9" r="2.5"/><path d="M15 20c0-2.5 1.6-4.5 4-5"/></svg>;
    case "rocket":
      return <svg {...common}><path d="M14 4c4 0 6 2 6 6 0 5-5 9-5 9l-4-4s4-5 9-5"/><path d="M9 15l-2 5 5-2"/><circle cx="15" cy="9" r="1.4"/></svg>;
    case "compass":
      return <svg {...common}><circle cx="12" cy="12" r="9"/><path d="M15 9l-2 6-6 2 2-6 6-2z"/></svg>;
    case "chat":
      return <svg {...common}><path d="M4 5h16v11H8l-4 4V5z"/></svg>;
    default: return null;
  }
}


function NotesStream({ c }) {
  const viewer = useViewer();
  const store = useAtsCandidateStore(c.id);
  const isAdmin = viewer.role === "admin";

  useEffectC(() => {
    let cancelled = false;
    fetchCandidateInterviews(c.id).then(interviews => {
      if (cancelled) return;
      const ratings = interviews.map(interview => {
        const sc = interview.scorecard || {};
        return {
          name: interview.interviewer_name || "Unknown",
          initials: (interview.interviewer_name || "?").split(" ").map(n => n[0]).join(""),
          color: interview.recommendation === "ai_evaluation" ? "#C4A265" : "#7595D5",
          role: interview.recommendation === "ai_evaluation" ? "AI evaluation" : interview.stage,
          feedback: interview.feedback,
          created_at: interview.created_at,
          id: interview.id,
          attachment_filename: interview.attachment_filename || null,
          craft: sc.craft ?? null, communication: sc.communication ?? null, culture: sc.culture ?? null,
          energy: sc.energy ?? null, startup: sc.startup ?? null, mission: sc.mission ?? null,
        };
      });
      __atsStore.setInterviews(c.id, ratings);
    });
    return () => { cancelled = true; };
  }, [c.id]);

  const handleDelete = async (interviewId) => {
    try {
      const resp = await fetch(`${API_BASE_URL}/candidates/${c.id}/interviews/${interviewId}`, {
        method: "DELETE",
        headers: { Authorization: `Bearer ${localStorage.getItem("ats_token")}` },
      });
      if (resp.ok) {
        const interviews = await fetchCandidateInterviews(c.id);
        const ratings = interviews.map(interview => {
          const sc = interview.scorecard || {};
          return {
            name: interview.interviewer_name || "Unknown",
            initials: (interview.interviewer_name || "?").split(" ").map(n => n[0]).join(""),
            color: interview.recommendation === "ai_evaluation" ? "#C4A265" : "#7595D5",
            role: interview.recommendation === "ai_evaluation" ? "AI evaluation" : interview.stage,
            feedback: interview.feedback,
            created_at: interview.created_at,
            id: interview.id,
            attachment_filename: interview.attachment_filename || null,
            craft: sc.craft ?? null, communication: sc.communication ?? null, culture: sc.culture ?? null,
            energy: sc.energy ?? null, startup: sc.startup ?? null, mission: sc.mission ?? null,
          };
        });
        __atsStore.setInterviews(c.id, ratings);
      } else {
        console.error("Delete interview failed:", resp.status);
      }
    } catch (err) {
      console.error("Delete interview error:", err);
    }
  };

  const interviewNotes = store.interviews
    .filter(r => r.feedback)
    .map(r => ({
      id: r.id || `interview-${r.created_at}`,
      author: {
        name: r.name || "Unknown",
        initials: r.initials || "?",
        color: r.color || "#9A9A9D",
        role: r.role === "ai_evaluation" ? "AI evaluation" : (STAGE_BY_ID[r.role] || {}).label || r.role || "",
      },
      time: r.created_at ? new Date(r.created_at).toLocaleDateString("en-US", { timeZone: "America/Los_Angeles", month: "short", day: "numeric" }) : "",
      body: React.createElement("div", { dangerouslySetInnerHTML: { __html: marked.parse(r.feedback, { breaks: true }) } }),
      canDelete: isAdmin || (r.name || "").toLowerCase() === (viewer.name || "").toLowerCase(),
      attached: r.attachment_filename ? { name: r.attachment_filename, url: `${API_BASE_URL}/candidates/${c.id}/interviews/${r.id}/attachment` } : null,
    }));
  const notes = [...store.notes, ...interviewNotes];
  if (!notes.length) {
    return (
      <div className="ats-notes-stream">
        <div style={{ padding: "20px 0", color: "var(--fg-3)", fontSize: 13 }}>No notes yet.</div>
      </div>
    );
  }
  return (
    <div className="ats-notes-stream">
      {notes.map((n, i) => <NotionNote key={n.id || i} n={n} onDelete={n.canDelete ? handleDelete : null} />)}
    </div>
  );
}

// ====== The detail body (shared between full page + modal) ======
// === Owner / next-interviewer picker ===
function OwnerPicker({ stageId, currentOwner, onPick, onClose, mode = "stage" }) {
  const [query, setQuery] = useStateC("");
  const stage = STAGE_BY_ID[stageId];
  const suggested = suggestedForStage(stageId);
  const suggestedIds = new Set(suggested.map(s => s.id));
  const others = TEAM.filter(t => !suggestedIds.has(t.id));
  const q = query.trim().toLowerCase();
  const filterFn = (t) => !q || t.name.toLowerCase().includes(q) || t.role.toLowerCase().includes(q);
  const filteredSuggested = suggested.filter(filterFn);
  const filteredOthers = others.filter(filterFn);
  const isOwnerMode = mode === "owner";

  useEffectC(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onClose]);

  return (
    <>
      <div className="ats-picker-scrim" onClick={onClose} />
      <div className="ats-owner-picker" onClick={(e) => e.stopPropagation()}>
        <div className="ats-owner-picker__head">
          <div>
            <div className="ats-owner-picker__title">
              {isOwnerMode ? (
                <>Reassign owner</>
              ) : (
                <>Who's running <b>{stage.label}</b>?</>
              )}
            </div>
            <div className="ats-owner-picker__sub">
              {isOwnerMode
                ? <>The owner DRIs this candidate end-to-end. They'll get nudges and notifications.</>
                : <>They'll be assigned as the candidate's owner for this stage.</>
              }
            </div>
          </div>
          <button className="ats-owner-picker__close" onClick={onClose}>✕</button>
        </div>
        <div className="ats-owner-picker__search">
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><circle cx="11" cy="11" r="7"/><path d="m20 20-4.3-4.3" strokeLinecap="round"/></svg>
          <input
            autoFocus
            value={query}
            onChange={(e) => setQuery(e.target.value)}
            placeholder="Search team — name or role"
          />
        </div>
        <div className="ats-owner-picker__body">
          {filteredSuggested.length > 0 && (
            <>
              <div className="ats-owner-picker__section">
                {isOwnerMode ? "Suggested" : `Suggested for ${stage.label.toLowerCase()}`}
              </div>
              {filteredSuggested.map(t => (
                <OwnerRow key={t.id} t={t} suggested onPick={onPick} isCurrent={t.name === currentOwner} />
              ))}
            </>
          )}
          {filteredOthers.length > 0 && (
            <>
              <div className="ats-owner-picker__section">Everyone</div>
              {filteredOthers.map(t => (
                <OwnerRow key={t.id} t={t} onPick={onPick} isCurrent={t.name === currentOwner} />
              ))}
            </>
          )}
          {filteredSuggested.length === 0 && filteredOthers.length === 0 && (
            <div className="ats-owner-picker__empty">No teammates match — invite someone new?</div>
          )}
        </div>
        <div className="ats-owner-picker__foot">
          <button className="ats-owner-picker__skip" onClick={() => onPick(null)}>
            Skip — assign later
          </button>
          <span className="ats-owner-picker__hint">Esc to cancel</span>
        </div>
      </div>
    </>
  );
}

function OwnerRow({ t, suggested = false, isCurrent = false, onPick }) {
  return (
    <button className={"ats-owner-row" + (isCurrent ? " is-current" : "")} onClick={() => onPick(t)}>
      <InitialDisc initials={t.initials} color={t.color} size={28} avatar={t.avatar_url} />
      <div className="ats-owner-row__main">
        <div className="ats-owner-row__name">
          {t.name}
          {isCurrent && <span className="ats-owner-row__tag">Current owner</span>}
          {suggested && !isCurrent && <span className="ats-owner-row__tag ats-owner-row__tag--suggested">Suggested</span>}
        </div>
        <div className="ats-owner-row__role">{t.role}</div>
      </div>
      <span className="ats-owner-row__chev">→</span>
    </button>
  );
}

// =====================================================================
// Editable meta-row fields — Source / Owner / Start date.
// All three sit in the candidate header. They share a common chip-shape
// (.ats-edit-chip) — hover reveals a pencil; click opens an inline editor.
// =====================================================================

const ChevronIcon = () => (
  <svg className="ats-edit-chip__chev" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
    <path d="M6 9l6 6 6-6"/>
  </svg>
);

function EditableSourceField({ source, referral, onChange, onSave }) {
  const [open, setOpen] = useStateC(false);
  const wrapRef = useRefC(null);
  const inputRef = useRefC(null);
  const savedRef = useRefC({ source, referral });
  const close = (s, r) => { setOpen(false); if (s !== savedRef.current.source || r !== savedRef.current.referral) { savedRef.current = { source: s, referral: r }; onSave?.(s, r); } };
  useEffectC(() => {
    if (!open) return;
    const onDoc = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) close(source, referral); };
    const onKey = (e) => { if (e.key === "Escape") close(source, referral); };
    document.addEventListener("mousedown", onDoc);
    window.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("mousedown", onDoc); window.removeEventListener("keydown", onKey); };
  }, [open, source, referral]);
  useEffectC(() => {
    if (open && source === "referral") {
      setTimeout(() => inputRef.current?.focus(), 30);
    }
  }, [open, source]);
  return (
    <span className="ats-detail__meta-item ats-edit-chip-wrap" ref={wrapRef}>
      <span className="ats-edit-chip__label">Source:</span>
      <button
        type="button"
        className={"ats-edit-chip" + (open ? " is-open" : "")}
        onClick={() => open ? close(source, referral) : setOpen(true)}
        title="Change source"
      >
        <SourcePill source={source} />
        {source === "referral" && referral && (
          <span className="ats-edit-chip__sub">{referral}</span>
        )}
        <ChevronIcon />
      </button>
      {open && (
        <div className="ats-edit-menu" role="menu">
          {SOURCES.map(s => (
            <button
              key={s.id}
              role="menuitem"
              className={"ats-edit-menu__item" + (s.id === source ? " is-current" : "")}
              onClick={() => {
                onChange(s.id, s.id === "referral" ? referral : "");
                if (s.id !== "referral") close(s.id, "");
              }}
            >
              <SourcePill source={s.id} />
              {s.id === source && (
                <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round" className="ats-edit-menu__check"><path d="M5 12l5 5L20 7"/></svg>
              )}
            </button>
          ))}
          {source === "referral" && (
            <div className="ats-edit-menu__referral">
              <label className="ats-edit-menu__referral-label">Referred by</label>
              <input
                ref={inputRef}
                type="text"
                className="ats-edit-menu__referral-input"
                placeholder="e.g. Edward Z., Lightspeed, Accel"
                value={referral || ""}
                onChange={(e) => onChange("referral", e.target.value)}
                onKeyDown={(e) => { if (e.key === "Enter") close("referral", e.target.value); }}
              />
            </div>
          )}
        </div>
      )}
    </span>
  );
}

function OwnerBlock({ owner, onClick, onRemind, reminderStatus }) {
  return (
    <div className="ats-owner-block">
      <span className="ats-owner-block__label">Owned by</span>
      <button
        type="button"
        className="ats-owner-block__chip"
        onClick={onClick}
        title="Reassign owner"
      >
        <InitialDisc initials={owner.initials} color={owner.color} size={20} avatar={owner.avatar_url} />
        <b>{owner.name}</b>
        <ChevronIcon />
      </button>
      {onRemind && reminderStatus === "sent" ? (
        <span className="ats-owner-block__remind ats-owner-block__remind--sent">
          ✓ DM'd on Slack
        </span>
      ) : onRemind && (
        <button
          type="button"
          className="ats-owner-block__remind"
          onClick={onRemind}
          disabled={reminderStatus === "sending"}
          title={`Send a Slack DM reminder to ${owner.name}`}
        >
          <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" aria-hidden="true">
            <path d="M6 8a6 6 0 0 1 12 0c0 7 3 9 3 9H3s3-2 3-9" strokeLinecap="round" strokeLinejoin="round"/>
            <path d="M10.3 21a1.94 1.94 0 0 0 3.4 0" strokeLinecap="round"/>
          </svg>
          {reminderStatus === "sending" ? "Sending…" : `Remind ${owner.name.split(" ")[0]}`}
        </button>
      )}
    </div>
  );
}

function fmtDate(iso) {
  if (!iso) return "—";
  const [y, m, d] = iso.split("-").map(Number);
  const dt = new Date(y, m - 1, d);
  return dt.toLocaleDateString("en-US", { timeZone: "America/Los_Angeles", month: "short", day: "numeric", year: "numeric" });
}

function EditableStartDateField({ value, onChange }) {
  const [open, setOpen] = useStateC(false);
  const wrapRef = useRefC(null);
  useEffectC(() => {
    if (!open) return;
    const onDoc = (e) => { if (wrapRef.current && !wrapRef.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === "Escape") setOpen(false); };
    document.addEventListener("mousedown", onDoc);
    window.addEventListener("keydown", onKey);
    return () => { document.removeEventListener("mousedown", onDoc); window.removeEventListener("keydown", onKey); };
  }, [open]);

  const setRelative = (days) => {
    const d = new Date();
    d.setDate(d.getDate() + days);
    onChange(d.toISOString().slice(0, 10));
  };

  return (
    <span className="ats-detail__meta-item ats-edit-chip-wrap" ref={wrapRef}>
      <span className="ats-edit-chip__label">Start date:</span>
      <button
        type="button"
        className={"ats-edit-chip" + (open ? " is-open" : "")}
        onClick={() => setOpen(o => !o)}
        title="Set candidate start date"
      >
        <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round" style={{ color: "var(--fg-3)" }}>
          <rect x="3" y="5" width="18" height="16" rx="2"/>
          <path d="M3 10h18M8 3v4M16 3v4"/>
        </svg>
        <b>{value ? fmtDate(value) : "Set date"}</b>
        <ChevronIcon />
      </button>
      {open && (
        <div className="ats-edit-menu ats-edit-menu--date" role="menu">
          <div className="ats-edit-menu__head">Candidate start date</div>
          <input
            type="date"
            className="ats-edit-menu__date-input"
            value={value || ""}
            onChange={(e) => onChange(e.target.value)}
            autoFocus
          />
          <div className="ats-edit-menu__chips">
            {[
              ["ASAP", 0],
              ["+ 2 wks", 14],
              ["+ 4 wks", 28],
              ["+ 8 wks", 56],
              ["+ 12 wks", 84],
            ].map(([label, days]) => (
              <button key={label} type="button" className="ats-edit-menu__chip"
                onClick={() => { setRelative(days); }}
              >{label}</button>
            ))}
          </div>
          <div className="ats-edit-menu__foot">
            <button type="button" className="ats-edit-menu__clear" onClick={() => onChange("")}>Clear</button>
            <button type="button" className="ats-edit-menu__done" onClick={() => setOpen(false)}>Done</button>
          </div>
        </div>
      )}
    </span>
  );
}

// Editable comp ask — click to turn into an inline text input.
// Persists on blur or Enter; Esc cancels.
function EditableCompField({ value, onChange, locked }) {
  const [editing, setEditing] = useStateC(false);
  const [draft, setDraft] = useStateC(value);
  const inputRef = useRefC(null);

  useEffectC(() => { if (editing) setDraft(value); }, [editing, value]);
  useEffectC(() => {
    if (editing && inputRef.current) {
      inputRef.current.focus();
      inputRef.current.select();
    }
  }, [editing]);

  const commit = () => {
    const v = (draft || "").trim();
    if (v && v !== value) onChange(v);
    setEditing(false);
  };
  const cancel = () => { setDraft(value); setEditing(false); };

  if (locked) {
    return (
      <span className="ats-detail__meta-item">
        <span className="ats-edit-chip__label">Comp ask:</span>
        <span title="You don't have permission to see compensation on this candidate.">
          <AccessLock reason="Comp visible to recruiters & admins" />
        </span>
      </span>
    );
  }

  return (
    <span className="ats-detail__meta-item ats-edit-chip-wrap">
      <span className="ats-edit-chip__label">Comp ask:</span>
      {editing ? (
        <input
          ref={inputRef}
          type="text"
          className="ats-edit-chip ats-edit-chip--input"
          value={draft}
          onChange={(e) => setDraft(e.target.value)}
          onBlur={commit}
          onKeyDown={(e) => {
            if (e.key === "Enter") { e.preventDefault(); commit(); }
            else if (e.key === "Escape") { e.preventDefault(); cancel(); }
          }}
          placeholder="e.g. $200–240k + 0.4%"
          size={Math.max(8, (draft || "").length + 1)}
        />
      ) : (
        <button
          type="button"
          className="ats-edit-chip"
          onClick={() => setEditing(true)}
          title="Edit candidate's compensation ask"
        >
          <b>{value || "Add comp ask"}</b>
        </button>
      )}
    </span>
  );
}

// =====================================================================
// Resume PDF viewer — renders actual PDFs via pdf.js, with toolbar for
// page nav, zoom, and download. Falls back to extracted text if no PDF.
// =====================================================================
function _uploadResumePdf(candidateId, file, onDone) {
  const form = new FormData();
  form.append("file", file);
  fetch(`${API_BASE_URL}/candidates/${candidateId}/resume`, {
    method: "POST",
    headers: { Authorization: `Bearer ${localStorage.getItem("ats_token")}` },
    body: form,
  }).then(r => {
    if (r.ok) onDone(true);
    else onDone(false);
  }).catch(() => onDone(false));
}

function ResumeViewer({ c, job }) {
  const [zoom, setZoom] = useStateC(1);
  const [page, setPage] = useStateC(1);
  const [totalPages, setTotalPages] = useStateC(1);
  const [pdfDoc, setPdfDoc] = useStateC(null);
  const [loading, setLoading] = useStateC(true);
  const [uploading, setUploading] = useStateC(false);
  const canvasRef = useRefC(null);
  const linkLayerRef = useRefC(null);
  const wrapRef = useRefC(null);
  const uploadRef = useRefC(null);
  const hasPdf = !!c._detail?.resume_file_key;
  const resumeUrl = `${API_BASE_URL}/candidates/${c.id}/resume`;

  const [uploadError, setUploadError] = useStateC(null);
  const handleUpload = (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    setUploading(true);
    setUploadError(null);
    _uploadResumePdf(c.id, file, (ok) => {
      setUploading(false);
      if (ok) window.location.reload();
      else setUploadError("Resume upload failed. Please try again.");
    });
    e.target.value = "";
  };

  const setZoomClamped = (z) => setZoom(Math.max(0.5, Math.min(3, +z.toFixed(2))));
  const fileName = `${c.name.replace(/\s+/g, "_")}_Resume.pdf`;

  // Load PDF document
  useEffectC(() => {
    if (!hasPdf) { setLoading(false); return; }
    let cancelled = false;
    setLoading(true);
    pdfjsLib.getDocument(resumeUrl).promise.then(doc => {
      if (cancelled) return;
      setPdfDoc(doc);
      setTotalPages(doc.numPages);
      setLoading(false);
    }).catch(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, [c.id, hasPdf]);

  // Render current page to canvas + link overlay
  useEffectC(() => {
    if (!pdfDoc || !canvasRef.current) return;
    let cancelled = false;
    pdfDoc.getPage(page).then(async (pg) => {
      if (cancelled) return;
      const scale = 1.5 * zoom;
      const viewport = pg.getViewport({ scale });
      const canvas = canvasRef.current;
      canvas.width = viewport.width;
      canvas.height = viewport.height;
      pg.render({ canvasContext: canvas.getContext("2d"), viewport });

      // Build clickable link overlay using percentages so it scales with CSS
      if (!linkLayerRef.current) return;
      const layer = linkLayerRef.current;
      layer.innerHTML = "";
      const vw = viewport.width, vh = viewport.height;
      try {
        const annotations = await pg.getAnnotations();
        for (const ann of annotations) {
          if (ann.subtype !== "Link") continue;
          const url = ann.url;
          const dest = ann.dest;
          if (!url && !dest) continue;
          const rect = ann.rect;
          const [x1, y1, x2, y2] = pdfjsLib.Util.normalizeRect(
            viewport.convertToViewportRectangle(rect)
          );
          const a = document.createElement("a");
          if (url) {
            a.href = url;
            a.target = "_blank";
            a.rel = "noopener noreferrer";
          } else if (dest) {
            a.href = "#";
            a.addEventListener("click", (e) => {
              e.preventDefault();
              if (typeof dest === "string") {
                pdfDoc.getDestination(dest).then(d => {
                  if (d) pdfDoc.getPageIndex(d[0]).then(idx => goToPage(idx + 1));
                });
              } else if (Array.isArray(dest)) {
                pdfDoc.getPageIndex(dest[0]).then(idx => goToPage(idx + 1));
              }
            });
          }
          a.style.cssText = "position:absolute;left:" + (x1/vw*100) + "%;top:" + (y1/vh*100) + "%;width:" + ((x2-x1)/vw*100) + "%;height:" + ((y2-y1)/vh*100) + "%;cursor:pointer;";
          layer.appendChild(a);
        }
      } catch (_) {}
    });
    return () => { cancelled = true; };
  }, [pdfDoc, page, zoom]);

  const goToPage = (n) => {
    setPage(Math.max(1, Math.min(totalPages, n)));
  };

  if (!hasPdf) {
    return <ResumeFallback c={c} />;
  }

  return (
    <div className="ats-resume">
      <div className="ats-resume__toolbar">
        <div className="ats-resume__file">
          <span className="ats-resume__file-icon" aria-hidden>
            <svg width="16" height="16" viewBox="0 0 24 24" fill="none"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z" stroke="currentColor" strokeWidth="1.6"/><path d="M14 3v5h5" stroke="currentColor" strokeWidth="1.6"/><text x="12" y="17" textAnchor="middle" fontSize="6" fontFamily="ui-monospace, monospace" fill="currentColor" fontWeight="700">PDF</text></svg>
          </span>
          <div className="ats-resume__file-meta">
            <b>{fileName}</b>
            <span>Resume</span>
          </div>
        </div>

        <div className="ats-resume__pager">
          <button className="ats-resume__icon-btn" onClick={() => goToPage(page - 1)} disabled={page === 1} title="Previous page" aria-label="Previous page">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M15 6l-6 6 6 6"/></svg>
          </button>
          <div className="ats-resume__pageinput">
            <input
              value={page}
              onChange={(e) => {
                const n = parseInt(e.target.value, 10);
                if (!isNaN(n)) goToPage(n);
              }}
              size={1}
              aria-label="Page number"
            />
            <span> / {totalPages}</span>
          </div>
          <button className="ats-resume__icon-btn" onClick={() => goToPage(page + 1)} disabled={page === totalPages} title="Next page" aria-label="Next page">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M9 6l6 6-6 6"/></svg>
          </button>
        </div>

        <div className="ats-resume__zoom">
          <button className="ats-resume__icon-btn" onClick={() => setZoomClamped(zoom - 0.1)} title="Zoom out" aria-label="Zoom out">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M5 12h14"/></svg>
          </button>
          <button className="ats-resume__zoom-val" onClick={() => setZoom(1)} title="Reset zoom">{Math.round(zoom * 100)}%</button>
          <button className="ats-resume__icon-btn" onClick={() => setZoomClamped(zoom + 0.1)} title="Zoom in" aria-label="Zoom in">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round"><path d="M12 5v14M5 12h14"/></svg>
          </button>
        </div>

        <div className="ats-resume__actions">
          <button className="ats-resume__action" title="Open in new tab"
                  onClick={() => window.open(resumeUrl, "_blank")}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M14 4h6v6"/><path d="M20 4l-9 9"/><path d="M20 14v6H4V4h6"/></svg>
          </button>
          <button className="ats-resume__action ats-resume__action--primary" title="Download"
                  onClick={() => {
                    const a = document.createElement("a");
                    a.href = resumeUrl;
                    a.download = fileName;
                    document.body.appendChild(a); a.click(); a.remove();
                  }}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 4v12"/><path d="M7 11l5 5 5-5"/><path d="M5 20h14"/></svg>
            <span>Download</span>
          </button>
          <input ref={uploadRef} type="file" accept=".pdf" style={{ display: "none" }} onChange={handleUpload} />
          <button className="ats-resume__action" title="Replace resume"
                  disabled={uploading}
                  onClick={() => uploadRef.current?.click()}>
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 20v-12"/><path d="M17 13l-5-5-5 5"/><path d="M5 4h14"/></svg>
            <span>{uploading ? "Uploading…" : "Replace"}</span>
          </button>
        </div>
        {uploadError && <div style={{ color: "#c0392b", fontSize: 12, padding: "0 12px" }}>{uploadError}</div>}
      </div>

      <div className="ats-resume__viewport" ref={wrapRef}>
        <div className="ats-resume__pages" style={{ ["--rz"]: zoom, display: "flex", justifyContent: "center", padding: "16px 0" }}>
          {loading
            ? <div style={{ padding: 40, color: "var(--fg-3)" }}>Loading PDF...</div>
            : <div style={{ position: "relative", display: "inline-block" }}>
                <canvas ref={canvasRef} style={{ display: "block", maxWidth: "100%" }} />
                <div ref={linkLayerRef} style={{ position: "absolute", top: 0, left: 0, width: "100%", height: "100%" }} />
              </div>
          }
        </div>
      </div>
    </div>
  );
}

function ResumeFallback({ c }) {
  const text = c._detail?.resume_parsed_text;
  const uploadRef = useRefC(null);
  const [uploading, setUploading] = useStateC(false);
  const [uploadError, setUploadError] = useStateC(null);
  const handleUpload = (e) => {
    const file = e.target.files?.[0];
    if (!file) return;
    setUploading(true);
    setUploadError(null);
    _uploadResumePdf(c.id, file, (ok) => {
      setUploading(false);
      if (ok) window.location.reload();
      else setUploadError("Resume upload failed. Please try again.");
    });
    e.target.value = "";
  };
  return (
    <div className="ats-resume">
      <div className="ats-resume__viewport" style={{ padding: 24 }}>
        {text && text !== "(PDF contained no extractable text)"
          ? <pre style={{ whiteSpace: "pre-wrap", fontFamily: "inherit", margin: 0, lineHeight: 1.6 }}>{text}</pre>
          : (
            <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 12, padding: "40px 0", color: "var(--fg-3)" }}>
              <svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
                <path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/>
              </svg>
              <p style={{ margin: 0 }}>No resume available</p>
              <input ref={uploadRef} type="file" accept=".pdf" style={{ display: "none" }} onChange={handleUpload} />
              <button
                className="ats-btn ats-btn--primary"
                disabled={uploading}
                onClick={() => uploadRef.current?.click()}
                style={{ fontSize: 13, padding: "6px 16px" }}
              >
                <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ marginRight: 6 }}><path d="M12 20v-12"/><path d="M17 13l-5-5-5 5"/><path d="M5 4h14"/></svg>
                {uploading ? "Uploading…" : "Upload resume"}
              </button>
              {uploadError && <div style={{ color: "#c0392b", fontSize: 12, marginTop: 8 }}>{uploadError}</div>}
            </div>
          )
        }
      </div>
    </div>
  );
}

function ResumeSection({ c, job }) {
  return (
    <div className="ats-notion-page ats-resume-section is-open">
      <div className="ats-resume-section__body">
        <ResumeViewer c={c} job={job} />
      </div>
    </div>
  );
}

function EmailMsg({ msg, defaultOpen }) {
  const [open, setOpen] = useStateC(defaultOpen);
  const initials = (msg.sender_name || "?").split(" ").map(p => p[0]).join("").slice(0, 2).toUpperCase();
  const teamMember = TEAM_BY_EMAIL[(msg.sender_email || "").toLowerCase()];
  const fmtDate = msg.message_date
    ? new Date(msg.message_date).toLocaleString("en-US", { timeZone: "America/Los_Angeles", month: "short", day: "numeric", hour: "numeric", minute: "2-digit" })
    : "";
  return (
    <div className={"ats-email-msg" + (open ? " is-open" : "")}>
      <button className="ats-email-msg__head" onClick={() => setOpen(o => !o)}>
        <InitialDisc initials={initials} color={teamMember ? teamMember.color : "#7595D5"} size={28} avatar={teamMember ? teamMember.avatar_url : null} />
        <div className="ats-email-msg__who">
          <div className="ats-email-msg__line">
            <b className="ats-email-msg__name">{msg.sender_name || msg.sender_email}</b>
            <span className="ats-email-msg__addr">&lt;{msg.sender_email}&gt;</span>
            <span className="ats-email-msg__date">{fmtDate}</span>
          </div>
          {!open ? (
            <div className="ats-email-msg__preview">{msg.snippet || (msg.body_text || "").slice(0, 120)}</div>
          ) : (
            <div className="ats-email-msg__meta">
              {msg.to_emails && msg.to_emails.length > 0 && <span><b>To:</b> {msg.to_emails.join(", ")}</span>}
              {msg.cc_emails && msg.cc_emails.length > 0 && <span><b>Cc:</b> {msg.cc_emails.join(", ")}</span>}
            </div>
          )}
        </div>
      </button>
      {open && (
        <div className="ats-email-msg__body">
          {(msg.body_text || msg.snippet || "").split("\n\n").map((p, i) => <p key={i}>{p}</p>)}
        </div>
      )}
    </div>
  );
}

function EmailTab({ candidateId }) {
  const [threads, setThreads] = useStateC([]);
  const [loading, setLoading] = useStateC(true);
  useEffectC(() => {
    fetchCandidateEmails(candidateId).then(t => { setThreads(t || []); setLoading(false); });
  }, [candidateId]);

  if (loading) return <div style={{ padding: 20, color: "var(--fg-3)", fontSize: 12.5 }}>Loading…</div>;
  if (!threads.length) return <div style={{ padding: 20, color: "var(--fg-3)", fontSize: 12.5 }}>No email threads yet. CC recruiting@nuancelabs.ai on a candidate conversation to see it here.</div>;

  const allMsgs = threads.flatMap(t => t.messages || []);
  const subject = (threads[0]?.subject || "Conversation").replace(/^Fwd:\s*/i, "").replace(/^Re:\s*/i, "");

  return (
    <div className="ats-email-tab">
      <div className="ats-email-tab__head">
        <h2 className="ats-email-tab__title" style={{ flex: 1 }}>{subject}</h2>
        <div className="ats-email-tab__head-meta">
          <span><b>{allMsgs.length}</b> messages</span>
          <span>·</span>
          <span>{threads.length} thread{threads.length > 1 ? "s" : ""}</span>
          <span>·</span>
          <span>Synced via Gmail</span>
        </div>
      </div>
      {threads.map(thread => (
        <div key={thread.thread_id} className="ats-email-thread">
          {(thread.messages || []).map((msg, i, arr) => (
            <EmailMsg key={msg.id} msg={msg} defaultOpen={i === arr.length - 1} />
          ))}
        </div>
      ))}
    </div>
  );
}

function ActivityTab({ candidateId }) {
  const [activities, setActivities] = useStateC([]);
  const [loading, setLoading] = useStateC(true);
  useEffectC(() => {
    fetchCandidateActivities(candidateId).then(a => { setActivities(a || []); setLoading(false); });
  }, [candidateId]);

  if (loading) return <div style={{ padding: 20, color: "var(--fg-3)", fontSize: 12.5 }}>Loading…</div>;
  if (!activities.length) return <div style={{ padding: 20, color: "var(--fg-3)", fontSize: 12.5 }}>No activity yet.</div>;

  const actionLabels = {
    candidate_created: "Profile created",
    stage_change: "Stage changed",
    email_candidate_created: "Created from email",
    brief_generated: "AI brief generated",
    evaluation_generated: "AI evaluation generated",
    interview_added: "Interview note submitted",
    note_added: "Note added",
    rejected: "Rejected",
    hired: "Hired",
  };

  return (
    <div className="ats-activity-tab">
      <ol className="ats-activity-feed">
        {activities.map((a, i) => {
          const details = a.details || {};
          const label = actionLabels[a.action_type] || a.action_type.replace(/_/g, " ");
          const extra = details.from && details.to
            ? <> — moved from <b>{details.from}</b> to <b>{details.to}</b></>
            : details.subject ? <> — <em>"{details.subject}"</em></> : "";
          const via = details.via || "";
          const fmtDate = new Date(a.created_at).toLocaleString("en-US", { timeZone: "America/Los_Angeles", month: "short", day: "numeric", hour: "numeric", minute: "2-digit" });
          return (
            <li key={a.id || i} className="ats-activity-row">
              <span className="ats-activity-row__actor">
                <InitialDisc initials="SY" color="#1D3140" size={16} />
                <b>{via || "System"}</b>
              </span>
              <span className="ats-activity-row__text">{label}{extra}</span>
              <span className="ats-activity-row__t">{fmtDate}</span>
            </li>
          );
        })}
      </ol>
    </div>
  );
}

function CandidateTabBar({ c, job, emailCount, activityCount }) {
  const viewer = useViewer();
  const hasResume = c._detail?.resume_file_key || c._detail?.resume_parsed_text;
  const hasLinkedin = c._detail?.linkedin_url;
  // Privacy: any offer-adjacent stage restricts non-admin viewers (interviewers +
  // external recruiters) to the Resume tab — no interview notes, AI brief, emails,
  // Slack, or activity. Offer discussions stay confidential. Note: backend uses
  // `declined_offer`; the frontend remaps it to `declined` via STAGE_MAP.
  const offerAdjacent = c.stage === "offer" || c.stage === "hired" || c.stage === "declined";
  const restrictedView = offerAdjacent && viewer.role !== "admin";
  const [tab, setTab] = useStateC(restrictedView ? "resume" : "overview");
  const tabs = restrictedView ? [
    { id: "resume", label: "Resume", count: null },
  ] : [
    { id: "overview",  label: "Overview",  count: null },
    ...(hasLinkedin ? [{ id: "linkedin", label: "LinkedIn", count: null }] : []),
    { id: "resume", label: "Resume", count: null },
    { id: "email",     label: "Emails",    count: emailCount },
    { id: "slack",     label: "Slack",     count: null },
    { id: "activity",  label: "Activity",  count: activityCount },
  ];
  return (
    <div className="ats-detail__main">
      <div className="ats-ctab-bar" role="tablist">
        {tabs.map(t => (
          <button
            key={t.id}
            role="tab"
            aria-selected={tab === t.id}
            className={"ats-ctab" + (tab === t.id ? " is-active" : "")}
            onClick={() => setTab(t.id)}
          >
            {t.label}
            {t.count != null && <span className="ats-ctab__count">{t.count}</span>}
          </button>
        ))}
      </div>
      <div className="ats-detail__content ats-detail__content--notion">
        {tab === "overview" && (
          <>
            <SubmitterCard c={c} />
            <AISummaryCard c={c} />
            <div className="ats-notion-page">
              <div className="ats-notion-page__sectionhead">
                <h2 className="ats-notion-page__title">Notes</h2>
                <span className="ats-notion-page__hint">
                  One running document for this candidate. Notes post as you add them — the panel reads
                  them at their leisure. Pin the ones that matter for the hire decision.
                </span>
              </div>
              <NotesComposer c={c} />
              <NotesStream c={c} />
            </div>
          </>
        )}
        {tab === "linkedin" && <LinkedInTab c={c} />}
        {tab === "resume" && <ResumeSection c={c} job={job} />}
        {tab === "email" && <EmailTab candidateId={c.id} />}
        {tab === "slack" && <SlackTab candidateId={c.id} />}
        {tab === "activity" && <ActivityTab candidateId={c.id} />}
      </div>
    </div>
  );
}

function SlackTab({ candidateId }) {
  const [threads, setThreads] = useStateC(null);
  const [loading, setLoading] = useStateC(true);

  useEffectC(() => {
    setLoading(true);
    fetch(`${API_BASE_URL}/slack/threads/${candidateId}`, {
      headers: { Authorization: `Bearer ${localStorage.getItem("ats_token")}` },
    })
      .then(r => {
        if (!r.ok) throw new Error(`${r.status}`);
        return r.json();
      })
      .then(data => { setThreads(data.threads || []); setLoading(false); })
      .catch(() => { setThreads([]); setLoading(false); });
  }, [candidateId]);

  if (loading) return <div style={{ padding: 20, color: "var(--fg-3)", fontSize: 13 }}>Loading Slack threads...</div>;
  if (!threads || threads.length === 0) return <div style={{ padding: 20, color: "var(--fg-3)", fontSize: 13 }}>No Slack threads linked to this candidate.</div>;

  return (
    <div className="ats-notion-page" style={{ padding: "16px 0" }}>
      {threads.map((thread, ti) => (
        <div key={ti} style={{ marginBottom: 24 }}>
          <div style={{ fontSize: 11, color: "var(--fg-4)", marginBottom: 10, textTransform: "uppercase", letterSpacing: "0.04em", fontWeight: 500 }}>
            Thread · {thread.messages.length} messages
          </div>
          {thread.messages.map((msg, mi) => (
            <div key={mi} style={{ display: "flex", gap: 10, marginBottom: 12, alignItems: "flex-start" }}>
              {msg.user_avatar
                ? <img src={msg.user_avatar} alt="" style={{ width: 28, height: 28, borderRadius: 6 }} />
                : <InitialDisc initials={(msg.user_name || "?").split(" ").map(n => n[0]).join("")} color="#7595D5" size={28} />
              }
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ display: "flex", alignItems: "baseline", gap: 8, marginBottom: 2 }}>
                  <span style={{ fontSize: 13, fontWeight: 600, color: "var(--nl-ink)" }}>{msg.user_name || "Unknown"}</span>
                  <span style={{ fontSize: 11, color: "var(--fg-4)", fontFamily: "var(--font-mono)" }}>
                    {new Date(msg.datetime).toLocaleString("en-US", { timeZone: "America/Los_Angeles", month: "short", day: "numeric", hour: "numeric", minute: "2-digit" })}
                  </span>
                </div>
                <div style={{ fontSize: 13, lineHeight: 1.5, color: "var(--fg-1)", whiteSpace: "pre-wrap" }}>{msg.text}</div>
              </div>
            </div>
          ))}
        </div>
      ))}
    </div>
  );
}

function LinkedInTab({ c }) {
  const url = c._detail?.linkedin_url;
  if (!url) return <div style={{ padding: 20, color: "var(--fg-3)", fontSize: 13 }}>No LinkedIn URL on file.</div>;
  const fullUrl = url.startsWith("http") ? url : `https://${url}`;
  return (
    <div className="ats-notion-page" style={{ padding: "20px 0" }}>
      <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 16 }}>
        <svg width="20" height="20" viewBox="0 0 24 24" fill="#0A66C2">
          <path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/>
        </svg>
        <a href={fullUrl} target="_blank" rel="noopener noreferrer"
           style={{ fontSize: 14, color: "#0A66C2", fontWeight: 500, textDecoration: "none" }}>
          {url.replace(/^https?:\/\//, "")}
        </a>
        <span style={{ fontSize: 11, color: "var(--fg-4)" }}>Opens in new tab</span>
      </div>
      {(c.title || c.company || c.location) && (
        <div style={{ padding: "14px 16px", background: "var(--bg-2)", borderRadius: 10, border: "1px solid var(--border-1)", fontSize: 13, lineHeight: 1.6 }}>
          {c.title && c.title !== "N/A" && <div><strong>Role:</strong> {c.title}</div>}
          {c.company && c.company !== "N/A" && <div><strong>Company:</strong> {c.company}</div>}
          {c.location && c.location !== "N/A" && <div><strong>Location:</strong> {c.location}</div>}
          {c.educationText && <div><strong>Education:</strong> {c.educationText}</div>}
          {c.companiesText && <div><strong>Companies:</strong> {c.companiesText}</div>}
        </div>
      )}
    </div>
  );
}

function CandidateDetailBody({ c: cProp, job, compact = false, onAdvance, onReject, onSetStage, onDelete }) {
  const [detail, setDetail] = useStateC(null);
  useEffectC(() => {
    let cancelled = false;
    fetchCandidateDetail(cProp.id).then(d => { if (!cancelled && d) setDetail(d); });
    return () => { cancelled = true; };
  }, [cProp.id]);
  const c = { ...cProp, _detail: detail };
  const [pickerOpen, setPickerOpen] = useStateC(null);
  const [stageMenuOpen, setStageMenuOpen] = useStateC(false);
  const [rejectOpen, setRejectOpen] = useStateC(false);
  const [deleteConfirmOpen, setDeleteConfirmOpen] = useStateC(false);
  const [deleting, setDeleting] = useStateC(false);
  const [reminderStatus, setReminderStatus] = useStateC(null);
  const [source, setSource] = useStateC(c.source);
  const [referral, setReferral] = useStateC(c.referredBy || "");
  const [owner, setOwner] = useStateC({
    name: c.recruiterName, initials: c.recruiterInitials, color: c.recruiterColor,
  });
  const [startDate, setStartDate] = useStateC(c._detail?.start_date || null);
  const [salary, setSalary] = useStateC(c.salary);
  const [editingName, setEditingName] = useStateC(false);
  const [draftName, setDraftName] = useStateC(c.name);
  const [displayName, setDisplayName] = useStateC(c.name);
  const nameRef = useRefC(null);
  const [editingRole, setEditingRole] = useStateC(false);
  const [draftRole, setDraftRole] = useStateC(c.title || "");
  const [displayRole, setDisplayRole] = useStateC(c.title || "");
  const roleRef = useRefC(null);
  const [jobPickerOpen, setJobPickerOpen] = useStateC(false);
  const [displayJob, setDisplayJob] = useStateC(job?.title || "Unassigned");
  const [displayJobId, setDisplayJobId] = useStateC(c.jobId);
  const next = nextStage(c.stage);
  const nextLabel = next ? STAGE_BY_ID[next].label : null;
  const viewer = useViewer();
  const canSeeComp = canSeeField(viewer, "comp");
  // Offer-stage stages are anchored for non-admins — they can't move the candidate
  // out of Offer/Hired/Declined-offer. Admins retain full control. Frontend stage id
  // for declined_offer is `declined` (see STAGE_MAP in api-data.jsx).
  const stageAnchored = (c.stage === "offer" || c.stage === "hired" || c.stage === "declined") && viewer.role !== "admin";
  return (
    <>
      <div className="ats-detail__head">
        <Avatar src={c.avatar} name={c.name} size={compact ? 56 : 72} ring />
        <div style={{ flex: 1 }}>
          {editingName ? (
            <input
              ref={nameRef}
              className="ats-detail__name ats-detail__name--input"
              value={draftName}
              onChange={e => setDraftName(e.target.value)}
              onBlur={() => {
                const v = draftName.trim();
                if (v && v !== displayName) { setDisplayName(v); updateCandidateProfile(c.id, { name: v }); }
                setEditingName(false);
              }}
              onKeyDown={e => {
                if (e.key === "Enter") { e.preventDefault(); e.target.blur(); }
                else if (e.key === "Escape") { setDraftName(displayName); setEditingName(false); }
              }}
              autoFocus
            />
          ) : (
            <h1
              className="ats-detail__name ats-detail__name--editable"
              onClick={() => { setDraftName(displayName); setEditingName(true); }}
              title="Click to edit name"
            >{displayName}</h1>
          )}
          {editingRole ? (
            <div className="ats-detail__sub">
              <input
                ref={roleRef}
                className="ats-detail__role-input"
                value={draftRole}
                onChange={e => setDraftRole(e.target.value)}
                onBlur={() => {
                  const v = draftRole.trim();
                  if (v !== displayRole) { setDisplayRole(v); updateCandidateProfile(c.id, { current_role: v }); }
                  setEditingRole(false);
                }}
                onKeyDown={e => {
                  if (e.key === "Enter") { e.preventDefault(); e.target.blur(); }
                  else if (e.key === "Escape") { setDraftRole(displayRole); setEditingRole(false); }
                }}
                autoFocus
              />
            </div>
          ) : (
            <div className="ats-detail__sub">
              <b
                className="ats-detail__role--editable"
                onClick={() => { setDraftRole(displayRole); setEditingRole(true); }}
                title="Click to edit role"
              >{displayRole || "Add role"}</b>
              {" "}at <b>{c.company}</b>
              <span style={{ position: "relative", display: "inline" }}>
                {" · candidate for "}
                <b
                  className="ats-detail__role--editable"
                  onClick={() => setJobPickerOpen(o => !o)}
                  title="Click to change job"
                >{displayJob}</b>
                {jobPickerOpen && (
                  <>
                    <div className="ats-picker-scrim" onClick={() => setJobPickerOpen(false)} style={{ zIndex: 99 }} />
                    <div className="ats-job-picker" style={{ zIndex: 100 }}>
                      {JOBS.filter(j => j.id !== displayJobId).map(j => (
                        <button
                          key={j.id}
                          className="ats-job-picker__item"
                          onClick={() => {
                            setDisplayJob(j.title);
                            setDisplayJobId(j.id);
                            setJobPickerOpen(false);
                            updateCandidateProfile(c.id, { job_id: j.id });
                          }}
                        >{j.title}</button>
                      ))}
                    </div>
                  </>
                )}
              </span>
            </div>
          )}
          {(() => {
            const links = [];
            const linkedinUrl = c._detail?.linkedin_url;
            if (linkedinUrl) {
              links.push({
                kind: "linkedin",
                label: "LinkedIn",
                href: linkedinUrl,
                tip: linkedinUrl.replace(/^https?:\/\//, ""),
                icon: (
                  <svg width="11" height="11" viewBox="0 0 24 24" fill="currentColor" aria-hidden>
                    <path d="M4.98 3.5C4.98 4.88 3.87 6 2.5 6S0 4.88 0 3.5 1.12 1 2.5 1s2.48 1.12 2.48 2.5zM.22 8h4.56v14H.22V8zm7.49 0h4.37v1.92h.06c.61-1.15 2.1-2.36 4.32-2.36 4.62 0 5.47 3.04 5.47 7v7.44h-4.55v-6.6c0-1.57-.03-3.6-2.19-3.6-2.19 0-2.53 1.71-2.53 3.48V22H7.71V8z"/>
                  </svg>
                ),
              });
            }
            if (c.email && !c.email.includes("placeholder")) {
              links.push({
                kind: "email",
                label: c.email,
                href: `mailto:${c.email}`,
                tip: c.email,
                icon: (
                  <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden>
                    <rect x="2" y="4" width="20" height="16" rx="2"/>
                    <path d="M22 7l-10 7L2 7"/>
                  </svg>
                ),
              });
            }
            if (!links.length) return null;
            return (
              <div className="ats-detail__links">
                {links.map((l) => (
                  <a
                    key={l.kind}
                    href={l.href}
                    target="_blank"
                    rel="noopener noreferrer"
                    className="ats-detail__link"
                    title={`${l.label} — ${l.tip}`}
                  >
                    <span className="ats-detail__link-icon">{l.icon}</span>
                    <span className="ats-detail__link-label">{l.label}</span>
                  </a>
                ))}
              </div>
            );
          })()}
          <div className="ats-detail__meta-row">
            {STAGE_BY_ID[c.stage] && <StageChip stage={STAGE_BY_ID[c.stage]} />}
            <span className="ats-detail__meta-item">📍 <b>{c.location}</b></span>
            <EditableSourceField
              source={source}
              referral={referral}
              onChange={(s, r) => { setSource(s); setReferral(r ?? ""); }}
              onSave={(s, r) => updateCandidateSource(c.id, s, r)}
            />
            {c.duplicateIds && c.duplicateIds.length > 0 && (
              <span className="ats-source-pill" data-src="duplicate">Duplicate</span>
            )}
            <EditableStartDateField value={startDate} onChange={(v) => { setStartDate(v); updateCandidateProfile(c.id, { start_date: v || "" }); }} />
            <span className="ats-detail__meta-item">In stage <b>{c.days}d</b></span>
            {(() => {
              // Days since last activity — derived deterministically per candidate.
              // Capped at days-in-stage so it never reads older than the stage entry.
              const seed = (c.id || "").split("").reduce((a, ch) => a + ch.charCodeAt(0), 0);
              const lastActivityDays = Math.min(c.days, 1 + (seed % Math.max(1, Math.min(c.days, 9))));
              const stale = lastActivityDays >= 7;
              const label = lastActivityDays === 0 ? "today" : lastActivityDays === 1 ? "1d ago" : `${lastActivityDays}d ago`;
              return (
                <span className="ats-detail__meta-item" title={c.lastActivity ? `${c.lastActivity}` : "Last activity"}>
                  Last activity{" "}
                  <b style={stale ? { color: "#8B3A26" } : undefined}>{label}</b>
                  {stale && (
                    <span
                      aria-hidden
                      title="No activity for a week — consider following up"
                      style={{
                        marginLeft: 4, padding: "1px 5px", borderRadius: 4,
                        background: "rgba(180, 85, 61, 0.12)", color: "#8B3A26",
                        fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: 0.2,
                      }}
                    >stale</span>
                  )}
                </span>
              );
            })()}
            <EditableCompField
              value={salary}
              onChange={(v) => { setSalary(v); updateCandidateProfile(c.id, { desired_compensation: v }); }}
              locked={!canSeeComp}
            />
          </div>
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 8, alignItems: "flex-end" }}>
          <div style={{ display: "flex", gap: 6, position: "relative" }}>
            {stageAnchored ? (
              <span
                style={{
                  display: "inline-flex", alignItems: "center", gap: 6,
                  padding: "6px 12px", fontSize: 12, color: "var(--fg-3)",
                  background: "var(--bg-2)", borderRadius: 6, border: "1px solid var(--border-1)",
                }}
                title="Offer-stage candidates are anchored. Only admins can change the stage."
              >
                <svg width="12" height="12" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6">
                  <rect x="3" y="7" width="10" height="7" rx="1.2" />
                  <path d="M5.5 7V5a2.5 2.5 0 0 1 5 0v2" strokeLinecap="round" />
                </svg>
                Stage locked
              </span>
            ) : (
              <>
                <button
                  className="ats-btn ats-btn--ghost-bordered ats-btn--danger-ghost"
                  onClick={() => setDeleteConfirmOpen(true)}
                >
                  Delete
                </button>
                <button
                  className="ats-btn ats-btn--ghost-bordered"
                  onClick={() => { setStageMenuOpen(o => !o); setPickerOpen(null); }}
                >
                  Move stage ▾
                </button>
                <button className="ats-btn ats-btn--ghost-bordered" onClick={() => setRejectOpen(true)}>
                  Reject
                </button>
                {next ? (
                  <button
                    className="ats-btn ats-btn--primary"
                    onClick={() => { setPickerOpen({ mode: "advance" }); setStageMenuOpen(false); }}
                    disabled={!onAdvance}
                  >
                    Advance to {nextLabel} →
                  </button>
                ) : (
                  <button className="ats-btn ats-btn--primary" disabled>
                    Final stage
                  </button>
                )}
              </>
            )}
            {stageMenuOpen && (
              <div className="ats-stage-menu" onMouseLeave={() => setStageMenuOpen(false)}>
                <div className="ats-stage-menu__head">Move to stage</div>
                {STAGE_FLOW.filter(s => s !== c.stage).map(s => (
                  <button
                    key={s}
                    className="ats-stage-menu__item"
                    onClick={() => { setPickerOpen({ mode: "move", stageId: s }); setStageMenuOpen(false); }}
                    disabled={!onSetStage}
                  >
                    <span className="ats-stage-menu__dot" style={{ background: STAGE_BY_ID[s].accent }} />
                    <span>{STAGE_BY_ID[s].label}</span>
                    <span className="ats-stage-menu__owner">choose →</span>
                  </button>
                ))}
                <div className="ats-stage-menu__divider" />
                <button className="ats-stage-menu__item ats-stage-menu__item--danger" onClick={() => { onSetStage && onSetStage("backburner", null); setStageMenuOpen(false); }}>
                  <span className="ats-stage-menu__dot" style={{ background: STAGE_BY_ID.backburner.accent }} />
                  <span>Backburner</span>
                </button>
                <button className="ats-stage-menu__item ats-stage-menu__item--danger" onClick={() => { onSetStage && onSetStage("not_interested", null); setStageMenuOpen(false); }}>
                  <span className="ats-stage-menu__dot" style={{ background: STAGE_BY_ID.not_interested.accent }} />
                  <span>Not interested in us</span>
                </button>
                <button className="ats-stage-menu__item ats-stage-menu__item--danger" onClick={() => { onSetStage && onSetStage("declined", null); setStageMenuOpen(false); }}>
                  <span className="ats-stage-menu__dot" style={{ background: STAGE_BY_ID.declined.accent }} />
                  <span>Declined offer</span>
                </button>
              </div>
            )}
            {pickerOpen && (
              <OwnerPicker
                stageId={
                  pickerOpen.mode === "advance" ? next :
                  pickerOpen.mode === "owner"   ? c.stage :
                  pickerOpen.stageId
                }
                currentOwner={owner.name}
                mode={pickerOpen.mode}
                onClose={() => setPickerOpen(null)}
                onPick={(picked) => {
                  if (pickerOpen.mode === "advance") {
                    onAdvance && onAdvance(picked);
                  } else if (pickerOpen.mode === "owner") {
                    if (picked) {
                      setOwner({ name: picked.name, initials: picked.initials, color: picked.color });
                      updateCandidateProfile(c.id, { assigned_recruiter_id: picked.id || "" });
                    }
                  } else {
                    onSetStage && onSetStage(pickerOpen.stageId, picked);
                  }
                  setPickerOpen(null);
                }}
              />
            )}
          </div>
          <OwnerBlock
            owner={owner}
            onClick={() => { setPickerOpen({ mode: "owner" }); setStageMenuOpen(false); }}
            reminderStatus={reminderStatus}
            onRemind={() => {
              setReminderStatus("sending");
              fetch(`${API_BASE_URL}/candidates/${c.id}/remind`, {
                method: "POST",
                headers: { Authorization: `Bearer ${localStorage.getItem("ats_token")}` },
              })
                .then(r => r.json())
                .then(d => setReminderStatus(d.ok ? "sent" : "error"))
                .catch(() => setReminderStatus("error"));
            }}
          />
        </div>
      </div>
      <StageFlow currentStageId={c.stage} />
      {c.duplicateIds && c.duplicateIds.length > 0 && (
        <div className="ats-dup-banner">
          <svg className="ats-dup-banner__icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
            <path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/>
            <line x1="12" y1="9" x2="12" y2="13"/>
            <line x1="12" y1="17" x2="12.01" y2="17"/>
          </svg>
          <span className="ats-dup-banner__text">
            <b>{c.duplicateIds.length} duplicate profile{c.duplicateIds.length > 1 ? "s" : ""}</b>
            {" — "}
            {c.duplicateIds.map((dupId, i) => {
              const dup = CANDIDATES.find(x => x.id === dupId);
              if (!dup) return null;
              const stageLabel = (STAGE_BY_ID[dup.stage] || {}).label || dup.stage;
              const dupJob = JOBS.find(j => j.id === dup.jobId);
              return (
                <React.Fragment key={dupId}>
                  {i > 0 && ", "}
                  <a
                    href={"#candidate/" + dupId}
                    className="ats-dup-banner__link"
                    onClick={(e) => {
                      e.preventDefault();
                      window.location.hash = "candidate/" + dupId;
                    }}
                  >
                    {dup.name} ({stageLabel}{dupJob ? " · " + dupJob.title : ""})
                  </a>
                </React.Fragment>
              );
            })}
          </span>
        </div>
      )}
      <div className="ats-detail" style={{ flex: 1 }}>
        <CandidateTabBar c={c} job={job} />
        <aside className="ats-detail__rail">
          <div className="ats-rail-block">
            <div className="ats-rail-label">Snapshot</div>
            <div className="ats-rail-row"><span className="ats-rail-key">Current stage</span><StageChip stage={STAGE_BY_ID[c.stage]} /></div>
            <div className="ats-rail-row"><span className="ats-rail-key">Days in stage</span><b>{c.days}d</b></div>
            {(() => {
              const seed = (c.id || "").split("").reduce((a, ch) => a + ch.charCodeAt(0), 0);
              const lastActivityDays = Math.min(c.days, 1 + (seed % Math.max(1, Math.min(c.days, 9))));
              const stale = lastActivityDays >= 7;
              return (
                <div className="ats-rail-row">
                  <span className="ats-rail-key">Since last activity</span>
                  <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
                    <b style={stale ? { color: "#8B3A26" } : undefined}>{lastActivityDays}d</b>
                    {stale && (
                      <span
                        title="No activity for a week"
                        style={{
                          padding: "1px 5px", borderRadius: 4,
                          background: "rgba(180, 85, 61, 0.12)", color: "#8B3A26",
                          fontFamily: "var(--font-mono)", fontSize: 10, letterSpacing: 0.2,
                        }}
                      >stale</span>
                    )}
                  </span>
                </div>
              );
            })()}
            <div className="ats-rail-row"><span className="ats-rail-key">Total in pipeline</span><b>{(() => {
              const created = new Date(c._detail?.created_at || c.created_at || Date.now());
              const days = Math.floor((Date.now() - created) / (1000 * 60 * 60 * 24));
              return `${days}d`;
            })()}</b></div>
          </div>

          <div className="ats-rail-block">
            <div className="ats-rail-label">Upcoming</div>
            <div className="ats-rail-row" style={{ fontSize: 12, color: "var(--fg-3)" }}>No upcoming interviews scheduled.</div>
          </div>

          <div className="ats-rail-block">
            <div className="ats-rail-label">Logistics</div>
            <div className="ats-rail-row"><span className="ats-rail-key">Ideal start</span><b>{startDate ? fmtDate(startDate) : "Not set"}</b></div>
            <div className="ats-rail-row"><span className="ats-rail-key">Location</span><b>{c.location && c.location !== "N/A" ? c.location : "Not specified"}</b></div>
          </div>

          {canSeeComp ? (
            <div className="ats-rail-block">
              <div className="ats-rail-label">Compensation</div>
              <div className="ats-rail-row"><span className="ats-rail-key">Asking</span><b>{c._detail?.desired_compensation || salary || "Not specified"}</b></div>
              {c._detail?.current_compensation && (
                <div className="ats-rail-row"><span className="ats-rail-key">Current</span><b>{c._detail.current_compensation}</b></div>
              )}
            </div>
          ) : (
            <div className="ats-rail-block">
              <div className="ats-rail-label">Compensation</div>
              <div className="ats-rail-locked">
                <AccessLock reason="Comp visible to recruiters & admins" />
                <span>Compensation details are restricted to recruiters and admins on this candidate.</span>
              </div>
            </div>
          )}

          <div className="ats-rail-block">
            <div className="ats-rail-label">Interview panel</div>
            {(() => {
              const interviews = ratingsFor(c.id);
              const unique = [];
              const seen = new Set();
              interviews.forEach(r => {
                const key = r.name || "Unknown";
                if (!seen.has(key)) { seen.add(key); unique.push(r); }
              });
              if (!unique.length) return <div className="ats-rail-row" style={{ fontSize: 12, color: "var(--fg-3)" }}>No interviewers assigned yet.</div>;
              return unique.map((r, i) => (
                <div key={r.name + i} className="ats-rail-row">
                  <span style={{ display: "flex", alignItems: "center", gap: 8 }}>
                    <InitialDisc initials={r.initials || "?"} color={r.color || "#9A9A9D"} size={18} />
                    <b>{r.name}</b>
                  </span>
                  <span style={{ color: "var(--fg-3)", fontSize: 11 }}>{r.role || ""}</span>
                </div>
              ));
            })()}
          </div>

          {c._detail?.tags && c._detail.tags.length > 0 && (
            <div className="ats-rail-block">
              <div className="ats-rail-label">Tags</div>
              <div style={{ display: "flex", flexWrap: "wrap", gap: 4 }}>
                {c._detail.tags.map(t => (
                  <span key={t} className="ats-source-pill">{t}</span>
                ))}
              </div>
            </div>
          )}
        </aside>
      </div>
      {rejectOpen && (
        <RejectDialog c={c} job={job} onClose={() => setRejectOpen(false)} onReject={onReject} />
      )}
      {deleteConfirmOpen && (
        <>
          <div className="ats-picker-scrim" onClick={() => setDeleteConfirmOpen(false)} style={{ zIndex: 9999 }} />
          <div className="ats-reject-dialog" onClick={e => e.stopPropagation()} style={{ zIndex: 10000, maxWidth: 420, minHeight: "auto" }}>
            <div className="ats-reject-dialog__head">
              <div>
                <div className="ats-reject-dialog__title">Delete <b>{c.name}</b>?</div>
                <div className="ats-reject-dialog__sub">This will permanently remove the candidate profile, all interview notes, and activity history. This cannot be undone.</div>
              </div>
              <button className="ats-owner-picker__close" onClick={() => setDeleteConfirmOpen(false)}>&#x2715;</button>
            </div>
            <div className="ats-reject-dialog__foot">
              <button className="ats-btn ats-btn--ghost" onClick={() => setDeleteConfirmOpen(false)}>Cancel</button>
              <button
                className="ats-btn ats-btn--danger"
                disabled={deleting}
                onClick={() => {
                  setDeleting(true);
                  fetch(`${API_BASE_URL}/candidates/${c.id}`, {
                    method: "DELETE",
                    headers: { Authorization: `Bearer ${localStorage.getItem("ats_token")}` },
                  })
                    .then(r => { if (!r.ok) throw new Error("Delete failed"); return r.json(); })
                    .then(() => { setDeleteConfirmOpen(false); onDelete && onDelete(); })
                    .catch(() => { setDeleting(false); alert("Failed to delete candidate. Please try again."); });
                }}
              >
                {deleting ? "Deleting..." : "Delete permanently"}
              </button>
            </div>
          </div>
        </>
      )}
    </>
  );
}

// === Reject dialog — compose email and send via Gmail ===
const REJECT_REASONS = [
  { id: "level",   label: "Level mismatch",        template: (name) => `Hi ${name},\n\nThank you again for taking the time to talk with us. After discussing internally, we don't think the level of this role is the right fit at this moment — we're looking for someone with more experience leading ambiguous multi-system projects end-to-end.\n\nWe were really impressed with your depth in {topic} and would love to stay in touch as we open roles that match. We'd also be happy to make intros to companies in our network where your background seems like a stronger match.\n\nWishing you the best,\nThe Nuance Labs Team` },
  { id: "skills",  label: "Skills gap",            template: (name) => `Hi ${name},\n\nThank you for the time and energy you put into our interview process. We ultimately decided not to move forward — the bar for {skill} in this role is very high, and other candidates in the pipeline came in with more direct experience in that specific area.\n\nThis is in no way a reflection on you as an engineer. We enjoyed our conversation and we'd welcome you to apply again in 6–12 months as you build more depth there, or for a different role on the team.\n\nWishing you the best,\nThe Nuance Labs Team` },
  { id: "culture", label: "Values / working style", template: (name) => `Hi ${name},\n\nThank you for spending time with our team. After discussing, we've decided not to move forward with your candidacy.\n\nThis call was a close one and the decision was about how we work together day-to-day rather than your abilities — we think you'll do excellent work and we appreciated every conversation.\n\nWishing you the best on what comes next,\nThe Nuance Labs Team` },
  { id: "comp",    label: "Compensation mismatch", template: (name) => `Hi ${name},\n\nThanks again for the great conversations over the past few weeks. Unfortunately we weren't able to close the gap on compensation for this role, so we're going to pause things here.\n\nIf the framing ever shifts on your end, or if we open a role where the band works better, we'd love to reconnect. Genuinely appreciated your time and the thoughtfulness you brought to every conversation.\n\nBest,\nThe Nuance Labs Team` },
  { id: "other",   label: "Other / custom",        template: (name) => `Hi ${name},\n\nThank you for taking the time to interview with Nuance. After careful consideration, we've decided to move forward with other candidates whose background more closely matches what we need right now.\n\nThis isn't a reflection on the quality of your work — we were genuinely impressed. We'd love to stay in touch and reconnect as future roles open up.\n\nWishing you the best,\nThe Nuance Labs Team` },
];
const REJECT_SUBJECT = (name, jobTitle) => `Update on your job application at Nuance Labs`;

function RejectDialog({ c, job, onClose, onReject }) {
  const tweaks = (typeof window !== 'undefined' && window.__atsTweaks) || { defaultCc: '', showCcField: true };
  const firstName = c.name.split(" ")[0];
  const [reasonId, setReasonId] = useStateC("level");
  const [cc, setCc] = useStateC(tweaks.defaultCc || "");
  const [subject, setSubject] = useStateC(REJECT_SUBJECT(firstName, job?.title || "the role"));
  const defaultBody = REJECT_REASONS[0].template(firstName);
  const [body, setBody] = useStateC(defaultBody);
  const [touchedBody, setTouchedBody] = useStateC(false);

  useEffectC(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onClose]);

  const pickReason = (id) => {
    setReasonId(id);
    if (!touchedBody) {
      const r = REJECT_REASONS.find(x => x.id === id);
      setBody(r.template(firstName));
    }
  };

  const sendViaGmail = () => {
    const to = encodeURIComponent(c.email || `${firstName.toLowerCase()}@example.com`);
    const su = encodeURIComponent(subject);
    const bd = encodeURIComponent(body);
    const ccPart = cc.trim() ? `&cc=${encodeURIComponent(cc.trim())}` : "";
    const url = `https://mail.google.com/mail/?view=cm&fs=1&to=${to}${ccPart}&su=${su}&body=${bd}`;
    window.open(url, "_blank", "noopener");
    onReject && onReject(reasonId);
    onClose();
  };
  const rejectSilently = () => {
    onReject && onReject(reasonId);
    onClose();
  };

  return (
    <>
      <div className="ats-picker-scrim" onClick={onClose} style={{ zIndex: 9999 }} />
      <div className="ats-reject-dialog" onClick={(e) => e.stopPropagation()} style={{ zIndex: 10000 }}>
        <div className="ats-reject-dialog__head">
          <div>
            <div className="ats-reject-dialog__title">Reject <b>{c.name}</b></div>
            <div className="ats-reject-dialog__sub">They'll be moved to Rejected. You can send a note or skip it.</div>
          </div>
          <button className="ats-owner-picker__close" onClick={onClose}>✕</button>
        </div>

        <div className="ats-reject-dialog__body">
          <label className="ats-reject-dialog__label">Reason</label>
          <div className="ats-reject-reasons">
            {REJECT_REASONS.map(r => (
              <button
                key={r.id}
                className={"ats-reject-reason" + (reasonId === r.id ? " is-active" : "")}
                onClick={() => pickReason(r.id)}
              >
                {r.label}
              </button>
            ))}
          </div>

          <label className="ats-reject-dialog__label">To</label>
          <div className="ats-reject-dialog__to">
            <Avatar src={c.avatar} name={c.name} size={20} />
            <span><b>{c.name}</b> &lt;{c.email || `${firstName.toLowerCase()}@example.com`}&gt;</span>
          </div>

          {tweaks.showCcField && (
            <>
              <label className="ats-reject-dialog__label">CC</label>
              <input
                className="ats-reject-dialog__input"
                value={cc}
                placeholder="Comma-separated emails…"
                onChange={(e) => setCc(e.target.value)}
              />
            </>
          )}

          <label className="ats-reject-dialog__label">Subject</label>
          <input
            className="ats-reject-dialog__input"
            value={subject}
            onChange={(e) => setSubject(e.target.value)}
          />

          <label className="ats-reject-dialog__label">
            Message
            <span className="ats-reject-dialog__label-hint">Edit freely — opens as a Gmail draft</span>
          </label>
          <textarea
            className="ats-reject-dialog__textarea"
            value={body}
            rows={10}
            onChange={(e) => { setBody(e.target.value); setTouchedBody(true); }}
          />
        </div>

        <div className="ats-reject-dialog__foot">
          <button className="ats-owner-picker__skip" onClick={rejectSilently}>
            Reject without email
          </button>
          <div style={{ display: "flex", gap: 6, alignItems: "center" }}>
            <span className="ats-owner-picker__hint">Esc to cancel</span>
            <button className="ats-btn ats-btn--primary ats-reject-dialog__send" onClick={sendViaGmail}>
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M4 6h16v12H4z"/><path d="m4 6 8 7 8-7"/></svg>
              Open Gmail draft →
            </button>
          </div>
        </div>
      </div>
    </>
  );
}
function CandidateDetail() {
  const c = CANDIDATES.find(x => x.stage === "onsite") || CANDIDATES[0];
  const job = JOBS.find(j => j.id === c.jobId);
  const [, showToast, ToastHost] = useAtsToast();
  return (
    <div className="ats-shell ats-shell--frame">
      <TopNav activeTab="Pipeline" />
      <div className="ats-body">
        <JobSidebar activeJobId={c.jobId} onSelect={(id) => {
          if (id === c.jobId) return;
          const sel = JOBS.find(j => j.id === id);
          if (sel) showToast(`${sel.title} selected — open its artboard from the canvas`);
          else if (id === "__all__") showToast("All roles selected");
          else if (id === null) showToast("Open roles selected");
          else showToast("Saved view selected");
        }} />
        <main className="ats-main">
          <CandidateDetailBody c={c} job={job} />
          {ToastHost}
        </main>
      </div>
    </div>
  );
}

// Modal / pop-up from Kanban — same body, framed identically
// When `embedded` is true (used as a standalone artboard), it picks a default
// candidate, fills the artboard, and skips the scrim/click-outside behavior.
function CandidateModal({ candidate, onClose, embedded = false, onNavigate, onAdvance, onReject, onSetStage, onDelete }) {
  // Use ref to ensure portal container is stable across renders (fixes React 18 portal cleanup issue)
  const portalRootRef = useRefC(null);
  if (!portalRootRef.current) {
    portalRootRef.current = document.getElementById('portal-root') || document.body;
  }

  useEffectC(() => {
    if (embedded || !candidate) return;
    document.body.style.overflow = "hidden";
    const onKey = (e) => {
      const tag = e.target.tagName;
      const isTyping = tag === "INPUT" || tag === "TEXTAREA" || e.target.isContentEditable;
      if (e.key === "Escape" && !isTyping) onClose && onClose();
      else if (!isTyping && (e.key === "ArrowDown" || e.key === "j")) { onNavigate && onNavigate(1); }
      else if (!isTyping && (e.key === "ArrowUp" || e.key === "k")) { onNavigate && onNavigate(-1); }
    };
    window.addEventListener("keydown", onKey);
    return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = ""; };
  }, [onClose, embedded, onNavigate, candidate]);

  const c = candidate || CANDIDATES.find(x => x.stage === "onsite") || CANDIDATES[0];
  if (!c) return null;
  const job = JOBS.find(j => j.id === c.jobId);
  const inner = (
    <div className={"ats-modal" + (embedded ? " ats-modal--embedded" : "")} onClick={(e) => e.stopPropagation()}>
      <div className="ats-modal__bar">
        <div className="ats-modal__bar-left">
          {!embedded && (
            <button className="ats-modal__close" onClick={onClose} title="Close (Esc)">
              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M18 6 6 18M6 6l12 12" strokeLinecap="round"/></svg>
            </button>
          )}
          <span className="ats-modal__breadcrumb">
            <span>Pipeline</span>
            <span className="ats-modal__crumb-sep">›</span>
            <span>{STAGE_BY_ID[c.stage].label}</span>
            <span className="ats-modal__crumb-sep">›</span>
            <b>{c.name}</b>
          </span>
        </div>
        <div className="ats-modal__bar-right">
          <button className="ats-modal__nav" title="Previous candidate (↑)" onClick={() => onNavigate && onNavigate(-1)} disabled={embedded}>↑</button>
          <button className="ats-modal__nav" title="Next candidate (↓)" onClick={() => onNavigate && onNavigate(1)} disabled={embedded}>↓</button>
        </div>
      </div>
      <div className="ats-modal__body">
        <CandidateDetailBody c={c} job={job} compact onAdvance={onAdvance} onReject={onReject} onSetStage={onSetStage} onDelete={onDelete} />
      </div>
    </div>
  );
  if (embedded) return inner;

  return ReactDOM.createPortal(
    <div className="ats-modal-scrim" onClick={onClose}>{inner}</div>,
    portalRootRef.current
  );
}

function AddCandidateModal({ onClose, defaultJobId }) {
  const [form, setForm] = useStateC({
    name: "", email: "", phone: "", linkedin_url: "",
    current_role: "", current_company: "", location: "",
    job_id: defaultJobId || "", source: "unknown", referrer: "",
    desired_compensation: "", start_date: "", notes: "",
  });
  const [touched, setTouched] = useStateC(false);
  const [submitting, setSubmitting] = useStateC(false);
  const [error, setError] = useStateC(null);
  const [resume, setResume] = useStateC(null);
  const [parsing, setParsing] = useStateC(false);
  const fileRef = useRefC(null);
  const set = (k) => (e) => setForm(f => ({ ...f, [k]: e.target.value }));

  const handleFile = async (f) => {
    if (!f) return;
    setResume(f);
    setParsing(true);
    try {
      const fd = new FormData();
      fd.append("file", f);
      const resp = await fetch(`${API_BASE_URL}/parse-resume`, { method: "POST", body: fd });
      if (resp.ok) {
        const data = await resp.json();
        if (data.parsed) {
          const fullName = [data.first_name, data.last_name].filter(Boolean).join(" ");
          setForm(prev => ({
            ...prev,
            name: prev.name || fullName || "",
            email: prev.email || data.email || "",
            phone: prev.phone || data.phone || "",
            linkedin_url: prev.linkedin_url || data.linkedin || "",
          }));
        }
      }
    } catch (err) { console.warn("Resume parse failed:", err); }
    finally { setParsing(false); }
  };

  useEffectC(() => {
    const onKey = (e) => { if (e.key === "Escape") onClose(); };
    window.addEventListener("keydown", onKey);
    return () => window.removeEventListener("keydown", onKey);
  }, [onClose]);

  const required = ["name", "email"];
  const missing = required.filter(k => !form[k].trim());

  const submit = async () => {
    if (missing.length) { setTouched(true); return; }
    setSubmitting(true);
    setError(null);
    try {
      const resp = await fetch(`${API_BASE_URL}/candidates`, {
        method: "POST",
        headers: { "Content-Type": "application/json", Authorization: `Bearer ${localStorage.getItem("ats_token")}` },
        body: JSON.stringify({
          ...form,
          job_id: form.job_id || null,
          referrer: form.referrer || null,
          notes: form.notes || null,
        }),
      });
      if (!resp.ok) {
        const d = await resp.json().catch(() => ({}));
        setError(d.detail || "Failed to create candidate");
        setSubmitting(false);
        return;
      }
      const candidate = await resp.json();
      if (resume && candidate.id) {
        const fd = new FormData();
        fd.append("file", resume);
        try {
          const uploadResp = await fetch(`${API_BASE_URL}/candidates/${candidate.id}/resume`, {
            method: "POST",
            headers: { Authorization: `Bearer ${localStorage.getItem("ats_token")}` },
            body: fd,
          });
          if (!uploadResp.ok) {
            setError("Candidate created but resume upload failed. You can re-upload from the candidate page.");
            setSubmitting(false);
            await initializeData();
            notifyCandidatesChanged();
            return;
          }
        } catch {
          setError("Candidate created but resume upload failed. You can re-upload from the candidate page.");
          setSubmitting(false);
          await initializeData();
          notifyCandidatesChanged();
          return;
        }
      }
      await initializeData();
      notifyCandidatesChanged();
      onClose();
    } catch (err) {
      setError(err.message);
      setSubmitting(false);
    }
  };

  const openJobs = JOBS.filter(j => j.status !== "closed");
  const portalRoot = document.getElementById("portal-root") || document.body;

  return ReactDOM.createPortal(
    <div className="ats-submit-scrim" onClick={onClose}>
      <div className="ats-submit" onClick={(e) => e.stopPropagation()}>
        <header className="ats-submit__head">
          <div>
            <h2 className="ats-submit__title">Add candidate</h2>
          </div>
          <button className="ats-modal__close" onClick={onClose} title="Close (Esc)">
            <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8"><path d="M18 6 6 18M6 6l12 12" strokeLinecap="round"/></svg>
          </button>
        </header>

        <div className="ats-submit__body">
          <div
            className={"ats-submit__drop" + (resume ? " is-filled" : "")}
            onClick={() => fileRef.current?.click()}
            onDragOver={(e) => e.preventDefault()}
            onDrop={(e) => { e.preventDefault(); handleFile(e.dataTransfer?.files?.[0]); }}
            role="button" tabIndex={0}
          >
            {!resume ? (
              <>
                <div className="ats-submit__drop-icon">
                  <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.4">
                    <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" strokeLinejoin="round"/>
                    <path d="M14 2v6h6M12 18v-6M9 15l3-3 3 3" strokeLinecap="round" strokeLinejoin="round"/>
                  </svg>
                </div>
                <div className="ats-submit__drop-title">Drop résumé to auto-fill</div>
                <div className="ats-submit__drop-sub">or click to browse · PDF up to 20MB</div>
              </>
            ) : (
              <>
                <div className="ats-submit__drop-icon ats-submit__drop-icon--ok">{parsing ? "⏳" : "✓"}</div>
                <div className="ats-submit__drop-title">{resume.name}</div>
                <div className="ats-submit__drop-sub">{(resume.size/1024).toFixed(0)} KB · {parsing ? "extracting info..." : "fields auto-filled"}</div>
                <button className="ats-btn" onClick={(e) => { e.stopPropagation(); setResume(null); }}>Replace</button>
              </>
            )}
            <input ref={fileRef} type="file" accept=".pdf" onChange={(e) => handleFile(e.target.files?.[0])} style={{ display: "none" }} />
          </div>

          <div className="ats-submit__divider"><span>Candidate</span></div>
          <div className="ats-submit__grid">
            <ACField label="Name" required missing={touched && !form.name.trim()}>
              <input className="ats-submit__input" value={form.name} onChange={set("name")} placeholder="Full name" autoFocus />
            </ACField>
            <ACField label="Email" required missing={touched && !form.email.trim()}>
              <input className="ats-submit__input" type="email" value={form.email} onChange={set("email")} placeholder="candidate@example.com" />
            </ACField>
            <ACField label="Phone" optional>
              <input className="ats-submit__input" value={form.phone} onChange={set("phone")} placeholder="+1 (555) 000-0000" />
            </ACField>
            <ACField label="LinkedIn" optional>
              <input className="ats-submit__input" value={form.linkedin_url} onChange={set("linkedin_url")} placeholder="linkedin.com/in/..." />
            </ACField>
            <ACField label="Current role" optional>
              <input className="ats-submit__input" value={form.current_role} onChange={set("current_role")} placeholder="Software Engineer" />
            </ACField>
            <ACField label="Company" optional>
              <input className="ats-submit__input" value={form.current_company} onChange={set("current_company")} placeholder="Acme Corp" />
            </ACField>
            <ACField label="Location" optional>
              <input className="ats-submit__input" value={form.location} onChange={set("location")} placeholder="Seattle, WA" />
            </ACField>
            <ACField label="Comp ask" optional>
              <input className="ats-submit__input" value={form.desired_compensation} onChange={set("desired_compensation")} placeholder="120k-140k" />
            </ACField>
          </div>

          <div className="ats-submit__divider"><span>Pipeline</span></div>
          <div className="ats-submit__grid">
            <ACField label="Role at Nuance" optional full>
              <select className="ats-submit__input" value={form.job_id} onChange={set("job_id")}>
                <option value="">Unassigned</option>
                {openJobs.map(j => <option key={j.id} value={j.id}>{j.title}</option>)}
              </select>
            </ACField>
            <ACField label="Source" optional>
              <select className="ats-submit__input" value={form.source} onChange={set("source")}>
                {SOURCES.map(s => <option key={s.id} value={s.id}>{s.label}</option>)}
              </select>
            </ACField>
            {form.source === "referral" && (
              <ACField label="Referrer" optional>
                <input className="ats-submit__input" value={form.referrer} onChange={set("referrer")} placeholder="Who referred them?" />
              </ACField>
            )}
          </div>

          <ACField label="Notes" optional full hint="Background, context, or anything worth capturing.">
            <textarea className="ats-submit__textarea" rows={3} value={form.notes} onChange={set("notes")}
              placeholder="Strong referral from the team — built real-time ML pipelines at Scale AI..." />
          </ACField>
        </div>

        <footer className="ats-submit__foot">
          <div className="ats-submit__foot-meta">
            {touched && missing.length > 0 && (
              <span className="ats-submit__warn">{missing.length} required field{missing.length === 1 ? "" : "s"} missing</span>
            )}
            {error && <span style={{ color: "#B4553D", fontSize: 12 }}>{error}</span>}
          </div>
          <button className="ats-btn" onClick={onClose}>Cancel</button>
          <button className="ats-btn ats-btn--primary" onClick={submit} disabled={submitting}>
            {submitting ? "Creating..." : "Add candidate"}
          </button>
        </footer>
      </div>
    </div>,
    portalRoot
  );
}

function ACField({ label, required, optional, missing, full, hint, children }) {
  return (
    <label className={"ats-submit__field" + (full ? " is-full" : "") + (missing ? " is-missing" : "")}>
      <span className="ats-submit__field-label">
        {label}
        {required && <span className="ats-submit__req">*</span>}
        {optional && <span className="ats-submit__opt">optional</span>}
      </span>
      {children}
      {hint && <span className="ats-submit__field-hint">{hint}</span>}
    </label>
  );
}

Object.assign(window, { CandidateDetail, CandidateDetailBody, CandidateModal, AddCandidateModal, NotionNote, NotesComposer, NotesStream });
