// =====================================================================
// External Recruiter Portal — what 3rd-party agency recruiters see when
// they log in. Stripped down: no kanban, no sidebar, just their submitted
// candidates grouped by role, with clear status updates.
// =====================================================================

const { useMemo: useMemoEX, useState: useStateEX, useRef: useRefEX } = React;

// ===== Submit candidate modal =====
function SubmitCandidateModal({ onClose, agency, viewer }) {
  const [resume, setResume] = useStateEX(null);
  const [form, setForm] = useStateEX({
    firstName: "", lastName: "", email: "", phone: "",
    linkedin: "", website: "", role: "",
    idealStart: "", relocateSeattle: "", inPerson: "",
    why: "", currentComp: "", expectedComp: "",
  });
  const [touched, setTouched] = useStateEX(false);
  const [draftSaved, setDraftSaved] = useStateEX(false);
  const [parsing, setParsing] = useStateEX(false);
  const fileRef = useRefEX(null);

  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
  const required = ["firstName","lastName","email","role","relocateSeattle","inPerson","why"];
  const missing = required.filter(k => !form[k]) ;
  const ready = resume && missing.length === 0;

  const onPick = () => fileRef.current?.click();
  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) {
          setForm(prev => ({
            ...prev,
            firstName: prev.firstName || data.first_name || "",
            lastName: prev.lastName || data.last_name || "",
            email: prev.email || data.email || "",
            phone: prev.phone || data.phone || "",
            linkedin: prev.linkedin || data.linkedin || "",
            website: prev.website || data.website || "",
          }));
        }
      }
    } catch (err) {
      console.warn("Resume parse failed:", err);
    } finally {
      setParsing(false);
    }
  };
  const onFile = (e) => { handleFile(e.target.files?.[0]); };
  const onDrop = (e) => { e.preventDefault(); handleFile(e.dataTransfer?.files?.[0]); };
  const onDrag = (e) => { e.preventDefault(); };

  const openRoles = JOBS.filter(j => j.status !== "inactive");

  return (
    <div className="ats-submit-scrim" onClick={onClose}>
      <div className="ats-submit" onClick={(e) => e.stopPropagation()}>
        <header className="ats-submit__head">
          <div>
            <div className="ats-submit__eyebrow">{agency ? agency.name : "Agency"} · Submit to Nuance</div>
            <h2 className="ats-submit__title">New candidate submission</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">
          {/* Resume drop zone — primary CTA */}
          <div
            className={"ats-submit__drop" + (resume ? " is-filled" : "")}
            onClick={onPick} onDragOver={onDrag} onDrop={onDrop}
            role="button" tabIndex={0}
          >
            {!resume ? (
              <>
                <div className="ats-submit__drop-icon">
                  <svg width="32" height="32" 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é here</div>
                <div className="ats-submit__drop-sub">or click to browse · PDF, DOC, DOCX up to 10MB</div>
                <div className="ats-submit__drop-hint">We'll auto-parse name, role, company & links to pre-fill the form below.</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,.doc,.docx" onChange={onFile} style={{ display: "none" }} />
          </div>

          {/* Form sections */}
          <div className="ats-submit__divider"><span>Candidate</span></div>
          <div className="ats-submit__grid">
            <Field label="First name" required missing={touched && !form.firstName}>
              <input className="ats-submit__input" value={form.firstName} onChange={(e) => set("firstName", e.target.value)} placeholder="Aria" />
            </Field>
            <Field label="Last name" required missing={touched && !form.lastName}>
              <input className="ats-submit__input" value={form.lastName} onChange={(e) => set("lastName", e.target.value)} placeholder="Chen" />
            </Field>
            <Field label="Email" required missing={touched && !form.email}>
              <input className="ats-submit__input" type="email" value={form.email} onChange={(e) => set("email", e.target.value)} placeholder="aria.chen@example.com" />
            </Field>
            <Field label="Phone" optional>
              <input className="ats-submit__input" value={form.phone} onChange={(e) => set("phone", e.target.value)} placeholder="+1 (555) 010-2345" />
            </Field>
            <Field label="LinkedIn" optional>
              <input className="ats-submit__input" value={form.linkedin} onChange={(e) => set("linkedin", e.target.value)} placeholder="linkedin.com/in/ariachen" />
            </Field>
            <Field label="Personal website" optional>
              <input className="ats-submit__input" value={form.website} onChange={(e) => set("website", e.target.value)} placeholder="ariachen.dev" />
            </Field>
          </div>

          <div className="ats-submit__divider"><span>Submission</span></div>
          <div className="ats-submit__grid">
            <Field label="Role at Nuance" required missing={touched && !form.role} full>
              <select className="ats-submit__input" value={form.role} onChange={(e) => set("role", e.target.value)}>
                <option value="">Select an open role…</option>
                {openRoles.map(j => (
                  <option key={j.id} value={j.id}>{j.title} · {j.dept} · {j.loc}</option>
                ))}
              </select>
            </Field>
            <Field label="Ideal start date" optional>
              <input className="ats-submit__input" type="date" value={form.idealStart} onChange={(e) => set("idealStart", e.target.value)} />
            </Field>
            <Field label="Current / target comp" optional>
              <input className="ats-submit__input" value={form.expectedComp} onChange={(e) => set("expectedComp", e.target.value)} placeholder="$220k base, open on equity" />
            </Field>
          </div>

          <div className="ats-submit__divider"><span>Fit confirmations</span></div>

          <Field label="Open to relocating to Seattle?" required missing={touched && !form.relocateSeattle} full>
            <SegRadio name="relocate" value={form.relocateSeattle} onChange={(v) => set("relocateSeattle", v)}
              options={[
                { v: "yes", label: "Yes, willing to relocate" },
                { v: "already", label: "Already in Seattle" },
                { v: "maybe", label: "Open to discuss" },
                { v: "no", label: "No / remote only" },
              ]} />
          </Field>

          <Field label="Open to 5 days/week in person?" required missing={touched && !form.inPerson} full>
            <SegRadio name="inperson" value={form.inPerson} onChange={(v) => set("inPerson", v)}
              options={[
                { v: "yes", label: "Yes, all 5 days" },
                { v: "hybrid", label: "Prefers hybrid (3-4 days)" },
                { v: "no", label: "Remote-only" },
              ]} />
          </Field>

          <Field label="Why is this candidate interested in Nuance Labs?" required missing={touched && !form.why} full
            hint="A few sentences from your screen — what excites them about Nuance specifically.">
            <textarea className="ats-submit__textarea" rows={4} value={form.why} onChange={(e) => set("why", e.target.value)}
              placeholder="Their thesis on real-time multimodal aligns with Nuance's roadmap; they followed the Whisper-2 launch closely and want to work with Edward's team on…" />
          </Field>
        </div>

        <footer className="ats-submit__foot">
          <div className="ats-submit__foot-meta">
            {resume ? <span className="ats-submit__chip">📎 {resume.name}</span> : <span className="ats-submit__chip ats-submit__chip--empty">No résumé attached</span>}
            {touched && missing.length > 0 && (
              <span className="ats-submit__warn">{missing.length} required field{missing.length === 1 ? "" : "s"} missing</span>
            )}
          </div>
          <button className="ats-btn ats-btn--ghost-bordered" style={{ padding: "8px 16px", fontSize: 13 }} onClick={onClose}>Cancel</button>
          <button
            className="ats-btn ats-btn--ghost-bordered"
            style={{ padding: "8px 16px", fontSize: 13 }}
            onClick={() => { setDraftSaved(true); setTimeout(() => setDraftSaved(false), 1800); }}
          >
            {draftSaved ? "✓ Draft saved" : "Save draft"}
          </button>
          <button
            className="ats-btn ats-btn--primary"
            style={{ padding: "8px 20px", fontSize: 13 }}
            onClick={async () => {
              if (!ready) { setTouched(true); return; }
              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({
                    name: `${form.firstName} ${form.lastName}`.trim(),
                    email: form.email,
                    phone: form.phone || null,
                    linkedin_url: form.linkedin || null,
                    job_id: form.role || null,
                    source: viewer?.sourceTag || agency?.source || "referral",
                    referrer: viewer?.sourceTag || agency?.name || null,
                    desired_compensation: form.expectedComp || null,
                    start_date: form.idealStart || null,
                    submitter_why: form.why || null,
                    relocation_willing: form.relocateSeattle || null,
                    in_person_willing: form.inPerson || null,
                  }),
                });
                if (resp.ok) {
                  const candidate = await resp.json();
                  let resumeFailed = false;
                  if (resume && candidate.id) {
                    const formData = new FormData();
                    formData.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: formData,
                      });
                      if (!uploadResp.ok) resumeFailed = true;
                    } catch { resumeFailed = true; }
                  }
                  await initializeData();
                  notifyCandidatesChanged();
                  if (resumeFailed) {
                    alert("Candidate submitted but resume upload failed. Please contact the hiring team to re-upload.");
                  }
                  onClose();
                } else {
                  const d = await resp.json().catch(() => ({}));
                  alert(d.detail || "Failed to submit candidate");
                }
              } catch (err) {
                alert("Error: " + err.message);
              }
            }}
            disabled={!resume}
          >
            Submit to Nuance
          </button>
        </footer>
      </div>
    </div>
  );
}

function Field({ 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>
  );
}

function SegRadio({ name, value, onChange, options }) {
  return (
    <div className="ats-submit__seg">
      {options.map(o => (
        <button
          key={o.v} type="button"
          className={"ats-submit__seg-btn" + (value === o.v ? " is-active" : "")}
          onClick={() => onChange(o.v)}
        >
          {o.label}
        </button>
      ))}
    </div>
  );
}

// ===== Ping DRI modal — slack-style direct message composer =====
function PingDriModal({ c, job, sender, onClose }) {
  // Map job manager (e.g. "Edward Z.") → TEAM member
  const driFromJob = TEAM.find(t => {
    const last = job?.manager?.split(" ").slice(-1)[0]?.replace(".", "");
    return last && t.name.includes(last);
  }) || TEAM[0];

  const [recipient, setRecipient] = useStateEX(driFromJob);
  const [picker, setPicker] = useStateEX(false);
  const [message, setMessage] = useStateEX("");
  const [includeContext, setIncludeContext] = useStateEX(true);
  const [sent, setSent] = useStateEX(false);

  // Suggested message templates depend on the candidate's stage
  const stageId = c.stage;
  const templates = (() => {
    if (["sourced","intro"].includes(stageId)) return [
      `Quick nudge — ${c.name.split(" ")[0]} is ${c.days}d in ${STAGE_BY_ID[stageId].label.toLowerCase()}. Should I keep them warm or move on?`,
      `${c.name.split(" ")[0]} just told me they have a competing offer with a one-week timeline. Worth pushing forward?`,
      `Any feedback I can pass along to ${c.name.split(" ")[0]}? They're asking what to expect next.`,
    ];
    if (["tech1","tech2","tech3"].includes(stageId)) return [
      `Heads up — ${c.name.split(" ")[0]} has another offer pending and asked about timeline. Can we tighten up the next round?`,
      `${c.name.split(" ")[0]} is asking about technical scope of the role. Anything specific I should share?`,
      `Just confirming the panel for ${c.name.split(" ")[0]}'s next round — same interviewers as last time?`,
    ];
    if (stageId === "onsite" || stageId === "offer") return [
      `${c.name.split(" ")[0]} is asking about comp structure & equity vest. What's the latest I can share?`,
      `Status check on ${c.name.split(" ")[0]}'s offer — they've got ${c.days}d on the clock and are asking about next steps.`,
      `Are we still on track for an offer this week? ${c.name.split(" ")[0]} has another timeline pressure.`,
    ];
    return [
      `Quick check-in on ${c.name.split(" ")[0]} — anything I can do to help move this forward?`,
      `${c.name.split(" ")[0]} asked me to follow up. Is there an update I can pass along?`,
    ];
  })();

  const drisForJob = [driFromJob, ...TEAM.filter(t => t.id !== driFromJob.id)].slice(0, 6);

  const send = () => {
    if (!message.trim()) return;
    setSent(true);
    setTimeout(onClose, 1400);
  };

  return (
    <div className="ats-submit-scrim" onClick={onClose}>
      <div className="ats-ping" onClick={(e) => e.stopPropagation()}>
        <header className="ats-ping__head">
          <div className="ats-ping__head-left">
            <div className="ats-ping__slack-icon">
              <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
                <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>
            </div>
            <div>
              <div className="ats-ping__eyebrow">Sends via Slack DM + email fallback</div>
              <h2 className="ats-ping__title">Send reminder to DRI</h2>
            </div>
          </div>
          <button className="ats-modal__close" onClick={onClose} title="Close">
            <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>

        {sent ? (
          <div className="ats-ping__sent">
            <div className="ats-ping__sent-icon">✓</div>
            <h3>Sent to {recipient.name.split(" ")[0]}</h3>
            <p>They'll see it in Slack as a DM from <b>Nuance ATS</b>. You'll get a Slack ping back when they reply.</p>
          </div>
        ) : (
          <div className="ats-ping__body">
            {/* Recipient picker */}
            <div className="ats-ping__field">
              <label>To</label>
              <div className="ats-ping__recipient-row">
                <button className="ats-ping__recipient" onClick={() => setPicker(p => !p)}>
                  <span className="ats-ping__avatar" style={{ background: recipient.color }}>
                    {recipient.initials}
                  </span>
                  <div>
                    <div className="ats-ping__recipient-name">{recipient.name}</div>
                    <div className="ats-ping__recipient-role">
                      {recipient.role}
                      {recipient.id === driFromJob.id && <span className="ats-ping__dri-badge">DRI for {job?.title.split(",")[0]}</span>}
                    </div>
                  </div>
                  <span className="ats-ping__caret">▾</span>
                </button>
                {picker && (
                  <div className="ats-ping__picker">
                    <div className="ats-ping__picker-head">Or send to a different teammate</div>
                    {drisForJob.map(t => (
                      <button key={t.id}
                        className={"ats-ping__picker-row" + (t.id === recipient.id ? " is-active" : "")}
                        onClick={() => { setRecipient(t); setPicker(false); }}>
                        <span className="ats-ping__avatar" style={{ background: t.color, fontSize: 9 }}>{t.initials}</span>
                        <span className="ats-ping__picker-name">{t.name}</span>
                        <span className="ats-ping__picker-role">{t.role}</span>
                        {t.id === driFromJob.id && <span className="ats-ping__dri-dot" title="DRI for this role" />}
                      </button>
                    ))}
                  </div>
                )}
              </div>
            </div>

            {/* About the candidate (auto-attached context card) */}
            <div className="ats-ping__field">
              <label>About</label>
              <div className="ats-ping__context-card">
                <div className="ats-ping__context-row">
                  <Avatar src={c.avatar} name={c.name} size={26} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div className="ats-ping__context-name">{c.name}</div>
                    <div className="ats-ping__context-sub">
                      {c.title} · {c.company} · for <b>{job?.title}</b>
                    </div>
                  </div>
                  <StageChip stage={STAGE_BY_ID[c.stage]} dense />
                </div>
                <div className="ats-ping__context-meta">
                  <span>Submitted by you · {c.days}d in current stage</span>
                  <label className="ats-ping__context-toggle">
                    <input type="checkbox" checked={includeContext}
                      onChange={(e) => setIncludeContext(e.target.checked)} />
                    Attach this context to your message
                  </label>
                </div>
              </div>
            </div>

            {/* Message templates */}
            <div className="ats-ping__field">
              <label>Suggested messages <span className="ats-ping__optional">tap to insert</span></label>
              <div className="ats-ping__templates">
                {templates.map((t, i) => (
                  <button key={i} className="ats-ping__template" onClick={() => setMessage(t)}>
                    {t}
                  </button>
                ))}
              </div>
            </div>

            {/* Composer */}
            <div className="ats-ping__field">
              <label>Message</label>
              <textarea
                className="ats-ping__composer"
                rows={4}
                placeholder={`Hey ${recipient.name.split(" ")[0]}, quick nudge on ${c.name.split(" ")[0]}…`}
                value={message}
                onChange={(e) => setMessage(e.target.value)}
              />
              <div className="ats-ping__composer-foot">
                <span className="ats-ping__composer-toolbar">
                  <button type="button" className="ats-ping__tool-btn" title="Bold"
                          onClick={() => setMessage(m => m + "**bold**")}>B</button>
                  <button type="button" className="ats-ping__tool-btn" title="Italic"
                          onClick={() => setMessage(m => m + "_italic_")}><i>i</i></button>
                  <button type="button" className="ats-ping__tool-btn" title="Code"
                          onClick={() => setMessage(m => m + "`code`")}>{"</>"}</button>
                  <button type="button" className="ats-ping__tool-btn" title="Mention"
                          onClick={() => setMessage(m => m + `@${recipient.name.split(" ")[0].toLowerCase()} `)}>@</button>
                  <button type="button" className="ats-ping__tool-btn" title="Emoji"
                          onClick={() => setMessage(m => m + "🙂")}>😀</button>
                </span>
                <span className="ats-ping__char-count">{message.length}/2000</span>
              </div>
            </div>
          </div>
        )}

        {!sent && (
          <footer className="ats-ping__foot">
            <span className="ats-ping__foot-meta">
              From <b>{sender?.name || "Agency Portal"}</b> · DM lands in {recipient.name.split(" ")[0]}'s Slack
            </span>
            <button className="ats-btn" onClick={onClose}>Cancel</button>
            <button className={"ats-btn ats-btn--primary" + (!message.trim() ? " is-disabled" : "")} onClick={send}>
              Send reminder →
            </button>
          </footer>
        )}
      </div>
    </div>
  );
}

function ExtPortalKpi({ value, label, hint }) {
  return (
    <div className="ats-portal__kpi">
      <div className="ats-portal__kpi-value">{value}</div>
      <div className="ats-portal__kpi-label">{label}</div>
      <div className="ats-portal__kpi-hint">{hint}</div>
    </div>
  );
}

// Compact status pill for a candidate. Re-uses the stage colors.
function ExtStatusBar({ c }) {
  const stage = STAGE_BY_ID[c.stage];
  const flowIdx = STAGE_FLOW.indexOf(c.stage);
  // Render the flow as 7 dots with the active one filled
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
      <div style={{ display: "flex", alignItems: "center", gap: 3 }}>
        {STAGE_FLOW.slice(0, 7).map((s, i) => {
          const reached = i <= flowIdx;
          const active = i === flowIdx;
          const stg = STAGE_BY_ID[s];
          return (
            <span
              key={s}
              title={stg.label}
              style={{
                width: active ? 16 : 6,
                height: 6,
                borderRadius: 3,
                background: reached ? stg.accent : "rgba(29,49,64,0.10)",
                transition: "all 120ms ease",
              }}
            />
          );
        })}
      </div>
      <StageChip stage={stage} dense />
    </div>
  );
}

function ExtCandidateRow({ c, onPing, onView }) {
  const job = JOBS.find(j => j.id === c.jobId);
  const isStale = c.days >= 14 && !["hired","rejected","not_interested","declined","backburner"].includes(c.stage);
  const blockedOnUs = ["sourced","intro"].includes(c.stage) && c.days >= 5;
  const blockedOnAgency = c.lastActivity.includes("Awaiting") && c.stage === "intro";

  // Deterministic synthesized timeline from candidate id
  const seed = (c.id || "").split("").reduce((s, ch) => s + ch.charCodeAt(0), 0);
  const stageIdx = STAGE_FLOW.indexOf(c.stage);
  // total days in pipeline: at least c.days, plus an offset proportional to how far along they are
  const stagesPassed = Math.max(0, stageIdx);
  const submittedDays = c.days + stagesPassed * 5 + (seed % 7);
  // days since last activity: less than days-in-current-stage, capped at 14
  const lastActivityDays = Math.min(c.days, 1 + (seed % Math.max(1, Math.min(c.days, 9))));

  const today = new Date(2025, 3, 24); // Apr 24 2025 — matches the schedule view
  const subDate = new Date(today.getTime() - submittedDays * 86400000);
  const subLabel = subDate.toLocaleDateString("en-US", { timeZone: "America/Los_Angeles", month: "short", day: "numeric" });

  return (
    <tr style={{ cursor: "pointer" }} onClick={() => onView && onView(c, job)}>
      <td>
        <div className="ats-sheet__name">
          <Avatar src={c.avatar} name={c.name} size={22} />
          <div style={{ display: "flex", flexDirection: "column", lineHeight: 1.2 }}>
            <b>{c.name}</b>
            <span className="ats-sheet__company">{c.title} · {c.company}</span>
          </div>
        </div>
      </td>
      <td><ExtStatusBar c={c} /></td>
      <td className="ats-portal__date-cell">
        <span className="ats-portal__date">{subLabel}</span>
        <span className="ats-portal__date-sub">{submittedDays}d ago</span>
      </td>
      <td>
        <span className={"ats-card__days" + (lastActivityDays >= 7 ? " is-stale" : "")}>
          {lastActivityDays}d
        </span>
      </td>
      <td className="ats-card__activity" style={{ maxWidth: 220 }}>
        {c.lastActivity}
        {blockedOnUs && (
          <span style={{ color: "var(--nl-blue)", fontSize: 10.5, marginLeft: 6 }}>
            · with Nuance
          </span>
        )}
        {blockedOnAgency && (
          <span style={{ color: "var(--nl-amber)", fontSize: 10.5, marginLeft: 6 }}>
            · awaiting your input
          </span>
        )}
      </td>
      <td style={{ textAlign: "right" }}>
        <div style={{ display: "inline-flex", gap: 6 }}>
          <button className="ats-btn ats-ping-btn" style={{ fontSize: 11.5 }}
                  onClick={(e) => { e.stopPropagation(); onPing && onPing(c, job); }}
                  title="Send reminder to the DRI">
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{ marginRight: 4, verticalAlign: -1 }}>
              <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>
            Send reminder
          </button>
          <button className="ats-btn ats-btn--ghost-bordered" style={{ fontSize: 11.5 }}
                  onClick={(e) => { e.stopPropagation(); onView && onView(c, job); }}>
            View ›
          </button>
        </div>
      </td>
    </tr>
  );
}

function ExtRoleGroup({ job, candidates, onPing, onView }) {
  const counts = candidates.reduce((acc, c) => {
    if (["sourced","intro","tech1","tech2","tech3","onsite"].includes(c.stage)) acc.live++;
    else if (c.stage === "offer") acc.offer++;
    else if (c.stage === "hired") acc.hired++;
    else if (c.stage === "rejected" || c.stage === "not_interested" || c.stage === "declined") acc.closed++;
    return acc;
  }, { live: 0, offer: 0, hired: 0, closed: 0 });
  return (
    <div className="ats-portal__role-group">
      <div className="ats-portal__role-head">
        <div>
          <div className="ats-portal__role-title">{job.title}</div>
          <div className="ats-portal__role-dept">
            {job.dept} · {job.loc} · Hiring manager {job.manager}
          </div>
        </div>
        <span className="ats-portal__role-count">
          {candidates.length} submitted
          {counts.live > 0 && ` · ${counts.live} live`}
          {counts.hired > 0 && ` · ${counts.hired} hired`}
        </span>
      </div>
      <div className="ats-sheet">
        <table>
          <thead>
            <tr>
              <th>Candidate</th>
              <th style={{ width: "26%" }}>Status</th>
              <th style={{ width: 100 }}>Submitted</th>
              <th style={{ width: 90 }}>Since activity</th>
              <th>Latest update</th>
              <th />
            </tr>
          </thead>
          <tbody>
            {candidates.map(c => <ExtCandidateRow key={c.id} c={c} onPing={onPing} onView={onView} />)}
          </tbody>
        </table>
      </div>
    </div>
  );
}

function ExternalPortal() {
  const viewer = useViewer();
  const [submitOpen, setSubmitOpen] = useStateEX(false);
  const [pingTarget, setPingTarget] = useStateEX(null); // { c, job }
  const [openCandidateId, setOpenCandidateId] = useStateEX(null);
  const [portalView, setPortalView] = useStateEX("board"); // "board" | "table"
  const [, showToast, ToastHost] = useAtsToast();
  const [filters, setFilters] = useStateEX({});
  const [sortOpen, setSortOpen] = useStateEX(false);
  const [sortLabel, setSortLabel] = useStateEX("most recent");
  // Force the viewer to be an external recruiter for this artboard
  const externalViewer = viewer.role === "external"
    ? viewer
    : VIEWERS.find(v => v.role === "external") || VIEWERS[0];

  const myCandidates = useMemoEX(
    () => visibleCandidates(externalViewer, CANDIDATES),
    [externalViewer]
  );

  // Group by job
  const byJob = useMemoEX(() => {
    const map = new Map();
    for (const c of myCandidates) {
      if (!map.has(c.jobId)) map.set(c.jobId, []);
      map.get(c.jobId).push(c);
    }
    return [...map.entries()].map(([jobId, list]) => ({
      job: JOBS.find(j => j.id === jobId),
      candidates: list,
    })).filter(x => x.job).sort((a, b) => b.candidates.length - a.candidates.length);
  }, [myCandidates]);

  const stats = {
    total: myCandidates.length,
    live: myCandidates.filter(c => ["intro","tech1","tech2","tech3","onsite"].includes(c.stage)).length,
    offer: myCandidates.filter(c => c.stage === "offer").length,
    hired: myCandidates.filter(c => c.stage === "hired").length,
  };

  const agency = EXT_AGENCIES.find(a => a.id === externalViewer.agencyId);

  return (
    <div className="ats-portal">
      {/* Top bar — minimal: just the brand and the agency context */}
      <header className="ats-portal__top">
        <div className="ats-portal__brand">
          <Mark size={20} />
          <span>Nuance</span>
          <small>Agency portal</small>
        </div>
        <div className="ats-portal__crumb">
          <span>You're viewing as</span>
          <b>{agency ? agency.name : "Agency"}</b>
        </div>
        <div className="ats-portal__top-spacer" />
        <div className="ats-portal__user">
          <Avatar name={externalViewer.name} size={28} ring />
          <div className="ats-portal__user-meta">
            <b>{externalViewer.name}</b>
            <span>{externalViewer.title}</span>
          </div>
        </div>
      </header>

      <div className="ats-portal__hero">
        <h1 className="ats-portal__title">
          Your submissions to Nuance Labs
        </h1>
        <p className="ats-portal__sub">
          Track every candidate you've sent in. You see status, notes, and comp on
          <em> your </em> submissions only — never anyone else's. Stage updates reflect what
          Nuance's hiring team has done in the last 14 days.
        </p>

        <div className="ats-portal__kpis">
          <ExtPortalKpi value={stats.total}  label="Submitted"   hint="across all open roles" />
          <ExtPortalKpi value={stats.live}   label="In process"  hint="intro through onsite" />
          <ExtPortalKpi value={stats.offer}  label="Offer stage" hint="comp negotiation" />
          <ExtPortalKpi value={stats.hired}  label="Hired"       hint="this quarter" />
        </div>
      </div>

      <main className="ats-portal__main">
        <div className="ats-portal__cta">
          <div className="ats-portal__cta-text">
            <b>You can submit candidates to any open role.</b> {" "}
            Nuance's hiring team responds within 3 business days. Submissions outside of
            agreed roles will be returned.
          </div>
          <button className="ats-btn ats-btn--primary ats-btn--page-action" onClick={() => setSubmitOpen(true)}>＋ Submit a candidate</button>
          <button className="ats-btn ats-btn--ghost-bordered ats-btn--page-action" onClick={() => {
            showToast("Jumped to active roles");
          }}>View open roles</button>
        </div>

        <div className="ats-portal__section-head">
          <h2 className="ats-portal__section-title">Your submissions</h2>
          <span className="ats-portal__section-sub">{myCandidates.length} {myCandidates.length === 1 ? "candidate" : "candidates"}</span>
          <div className="ats-portal__section-spacer" />
          <div style={{ display: "inline-flex", gap: 1, background: "rgba(29,49,64,0.08)", borderRadius: 8, padding: 3 }}>
            <button
              onClick={() => setPortalView("board")}
              style={{
                fontSize: 12, padding: "5px 14px", borderRadius: 6, fontWeight: 500,
                border: "none", cursor: "pointer",
                background: portalView === "board" ? "#fff" : "transparent",
                color: portalView === "board" ? "var(--nl-ink)" : "var(--fg-3)",
                boxShadow: portalView === "board" ? "0 1px 3px rgba(0,0,0,0.08)" : "none",
              }}
            >Board</button>
            <button
              onClick={() => setPortalView("table")}
              style={{
                fontSize: 12, padding: "5px 14px", borderRadius: 6, fontWeight: 500,
                border: "none", cursor: "pointer",
                background: portalView === "table" ? "#fff" : "transparent",
                color: portalView === "table" ? "var(--nl-ink)" : "var(--fg-3)",
                boxShadow: portalView === "table" ? "0 1px 3px rgba(0,0,0,0.08)" : "none",
              }}
            >Table</button>
          </div>
        </div>

        {portalView === "board" ? (
          <div className="ats-kanban" style={{ flex: 1, minHeight: 400 }}>
            {STAGES.filter(s => !["not_interested"].includes(s.id)).map(s => {
              const stageCandidates = myCandidates.filter(c => c.stage === s.id);
              return (
                <KanbanColumn
                  key={s.id}
                  stage={s}
                  candidates={stageCandidates}
                  onOpen={(id) => setOpenCandidateId(id)}
                  compact
                />
              );
            })}
          </div>
        ) : (
          <>
            {byJob.map(({ job, candidates }) => (
              <ExtRoleGroup key={job.id} job={job} candidates={candidates}
                onPing={(c, j) => setPingTarget({ c, job: j })}
                onView={(c) => setOpenCandidateId(c.id)} />
            ))}
          </>
        )}

        {myCandidates.length === 0 && (
          <div className="ats-myc-empty">
            <b>No submissions yet.</b>
            <span>Submit a candidate to start tracking them here.</span>
          </div>
        )}
      </main>

      {submitOpen && <SubmitCandidateModal onClose={() => setSubmitOpen(false)} agency={agency} viewer={externalViewer} />}
      {pingTarget && <PingDriModal c={pingTarget.c} job={pingTarget.job} sender={externalViewer} onClose={() => setPingTarget(null)} />}
      {openCandidateId && (
        <CandidateModal
          candidate={myCandidates.find(x => x.id === openCandidateId)}
          onClose={() => setOpenCandidateId(null)}
          onNavigate={(dir) => {
            const idx = myCandidates.findIndex(x => x.id === openCandidateId);
            const next = myCandidates[idx + dir];
            if (next) setOpenCandidateId(next.id);
          }}
        />
      )}
      {ToastHost}
    </div>
  );
}

Object.assign(window, { ExternalPortal, PingDriModal });
