/* ELEV8 ATS - Applicants: the high-volume inbound triage firehose across every job + board.
   Cross-job table (the home) + a focused Review mode (SwipeTriage). AI screens are advisory with an
   autonomy dial (Suggest -> Draft -> Auto). Qualify promotes the applicant into Candidates + the
   applied job's pipeline; Reject captures a reason. Both are reversible (toasts carry Undo).
   Data: window.E8DATA.applications. The per-job Inbound tab is a filtered view of the same data. */
const DSap = window.Stand8DesignSystem_b5c975;

/* Verdict bands ride the match-tier color ramp so applicants read like the matching tab
   (tier 1 = best); the pill itself stays a separate component from MatchBadge. */
const AP_VERDICT_TIER = { Strong: 1, Maybe: 3, Weak: 4 };
const AP_SOURCE_ICON = { LinkedIn: 'work', Indeed: 'search', Monster: 'travel_explore', CareerBuilder: 'badge', 'Careers site': 'public', 'Job board': 'list_alt', Referral: 'group' };
const AP_SEGMENTS = [{ id: 'new', stage: 'New', label: 'New' }, { id: 'reviewing', stage: 'Reviewing', label: 'Reviewing' }, { id: 'qualified', stage: 'Qualified', label: 'Qualified' }, { id: 'rejected', stage: 'Rejected', label: 'Rejected' }];
const AP_AUTONOMY = [{ id: 'suggest', label: 'Suggest' }, { id: 'draft', label: 'Draft' }, { id: 'auto', label: 'Auto' }];

function ApplicantSourceChip({ source, dupe }) {
  return (
    <span className="e8-ap-src">
      <span className="material-symbols-outlined" aria-hidden="true">{AP_SOURCE_ICON[source] || 'public'}</span>
      {source}
      {dupe ? <span className="e8-ap-dupe" title={'Same person also applied via ' + dupe + ' - merged'}>+{dupe}</span> : null}
    </span>
  );
}

function ApplicantAiCell({ a }) {
  if (a.aiScreen === 'Pending' || a.score == null) return <DSap.Badge tone="info" dot>Pending</DSap.Badge>;
  const t = AP_VERDICT_TIER[a.aiScreen] || 3;
  return (
    <span style={{ display: 'inline-flex', alignItems: 'center', gap: 7, flexWrap: 'wrap' }}>
      <span className="e8-ap-verdict" style={{ color: 'var(--ui-tier-' + t + ')', background: 'var(--ui-tier-' + t + '-tint)', borderColor: 'var(--ui-tier-' + t + '-br)' }}>
        <span className="e8-ap-verdict-dot" style={{ background: 'var(--ui-tier-' + t + '-dot)' }}></span>
        {a.aiScreen}
        <span className="tnum" style={{ opacity: 0.65 }}>{a.score}</span>
      </span>
      {a.knockout ? <span className="e8-ap-ko" title="Knockout - misses a must-have requirement">knockout</span> : null}
    </span>
  );
}

function ApplicantReasons({ reasons }) {
  if (!reasons || !reasons.length) return <div style={{ fontSize: 'var(--ui-text-sm)', color: 'var(--ui-text-tertiary)' }}>Not screened yet.</div>;
  return (
    <div className="e8-ap-reasons">
      {reasons.map((r, i) => (
        <div key={i} className={'e8-ap-reason ' + (r.s === '+' ? 'pos' : 'neg')}>
          <span className="material-symbols-outlined" aria-hidden="true">{r.s === '+' ? 'add_circle' : 'remove_circle'}</span>
          <span>{r.t}</span>
        </div>
      ))}
    </div>
  );
}

/* ---- Deterministic mock-profile generators (stable per applicant; no real backend) ---- */
function apHash(s) { let h = 2166136261; s = String(s || ''); for (let i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; }
function apPick(arr, n) { const L = arr.length; return arr[((Math.trunc(n) % L) + L) % L]; }
const AP_SKILLS = {
  Java: ['Java 17', 'Spring Boot', 'Microservices', 'Kafka', 'PostgreSQL', 'AWS', 'Docker', 'REST APIs'],
  Cloud: ['AWS', 'IAM', 'Terraform', 'SIEM', 'Zero Trust', 'Python', 'Kubernetes', 'Vuln management'],
  Data: ['SQL', 'Python', 'Power BI', 'Tableau', 'Snowflake', 'dbt', 'ETL', 'Statistics'],
  ServiceNow: ['ServiceNow', 'ITSM', 'Flow Designer', 'Scripting', 'CMDB', 'Integrations', 'ITIL'],
  Scrum: ['Scrum', 'SAFe', 'Jira', 'Kanban', 'Facilitation', 'Stakeholder mgmt', 'Coaching'],
  Salesforce: ['Salesforce', 'Apex', 'Flows', 'Reports', 'Security model', 'Sales Cloud', 'LWC'],
  General: ['Agile', 'Git', 'CI/CD', 'SQL', 'Communication', 'Problem solving'],
};
const AP_PRIOR_TITLES = {
  Java: ['Software Engineer', 'Backend Developer'], Cloud: ['Security Engineer', 'Cloud Engineer'],
  Data: ['Data Analyst', 'BI Developer'], ServiceNow: ['ServiceNow Developer', 'IT Analyst'],
  Scrum: ['Project Coordinator', 'Delivery Lead'], Salesforce: ['CRM Analyst', 'Salesforce Admin'],
  General: ['Analyst', 'Specialist'],
};
const AP_ORGS = ['TekSystems', 'Lokion', 'FedEx Dataworks', 'ServiceMaster', 'Regions Bank', 'AutoZone', 'Sedgwick', 'Buckman', 'Nike'];
const AP_DEGREES = ['B.S. Computer Science', 'B.S. Information Systems', 'B.S. Software Engineering', 'B.A. Mathematics', 'B.S. Data Science'];
const AP_SCHOOLS = ['University of Memphis', 'Mississippi State', 'Christian Brothers University', 'University of Tennessee', 'Rhodes College'];
const AP_OUTCOMES = ['Not selected', 'Client passed', 'Withdrew', 'Rejected - rate', 'Filled before review', 'Shortlisted, no offer'];
const AP_WHENS = ['3 weeks ago', '2 months ago', '4 months ago', '6 months ago', 'last quarter'];
const AP_NOTE_TEMPLATES = [
  'Spoke briefly at a meetup - sharp, worth a real screen.',
  'Came in via a referral last year; timing was off then.',
  'Strong on paper; confirm comp expectations early.',
  'Prefers contract-to-hire; flagged for the FedEx pod.',
  'Has worked with this client before - warm intro possible.',
];
function apDomain(text) {
  const t = (text || '').toLowerCase();
  if (/java|backend|full-?stack|software eng/.test(t)) return 'Java';
  if (/cloud|security|devsecops|network sec/.test(t)) return 'Cloud';
  if (/data|analyst|analytics|\bbi\b/.test(t)) return 'Data';
  if (/servicenow/.test(t)) return 'ServiceNow';
  if (/scrum|agile|project|coach/.test(t)) return 'Scrum';
  if (/salesforce|crm/.test(t)) return 'Salesforce';
  return 'General';
}
function buildResume(a, jobTitle) {
  const h = apHash(a.id || a.name);
  const dom = apDomain(a.title + ' ' + (jobTitle || ''));
  const skills = AP_SKILLS[dom] || AP_SKILLS.General;
  const years = 4 + (h % 9);
  const startYear = 2025 - years;
  const midYear = 2025 - Math.max(2, Math.floor(years / 2));
  const curStart = 2025 - Math.min(years, 2 + (h % 3));
  return {
    years,
    summary: a.title + ' with ~' + years + ' years in ' + dom.toLowerCase() + '. ' +
      (a.aiScreen === 'Strong' ? 'Strong match on the must-have stack for this role.'
        : a.aiScreen === 'Weak' ? 'Adjacent background - would need ramp on the core stack.'
        : 'Solid fundamentals; a screen will confirm depth.'),
    skills: skills.slice(0, 6),
    experience: [
      { role: a.title, org: a.company, span: curStart + ' - Present', bullet: 'Owns ' + dom.toLowerCase() + ' delivery - ' + apPick(skills, h).toLowerCase() + ' across production systems.' },
      { role: apPick(AP_PRIOR_TITLES[dom], h >> 3), org: apPick(AP_ORGS, h >> 5), span: startYear + ' - ' + midYear, bullet: 'Shipped features using ' + apPick(skills, h >> 2) + ' and ' + apPick(skills, h >> 4) + '.' },
    ],
    education: apPick(AP_DEGREES, h) + ' · ' + apPick(AP_SCHOOLS, h >> 6),
  };
}
function buildHistory(a) {
  const D = window.E8DATA;
  const h = apHash((a.id || a.name) + 'hist');
  const count = h % 3; // 0..2 prior applications
  if (!count) return [];
  const others = (D.jobs || []).filter((j) => j.id !== a.jobId && j.title);
  const out = [];
  for (let i = 0; i < count; i++) {
    const j = others[(h >> (i * 3)) % others.length];
    if (j) out.push({ jobId: j.id, role: j.title, client: j.client, when: apPick(AP_WHENS, h >> (i * 2)), outcome: apPick(AP_OUTCOMES, h >> (i + 1)) });
  }
  return out;
}
function seedNotes(a) {
  const h = apHash((a.id || a.name) + 'note');
  if (h % 2) return [];
  return [{ author: a.owner || 'Recruiter', when: apPick(['1 week ago', '3 weeks ago', '2 months ago'], h), text: apPick(AP_NOTE_TEMPLATES, h >> 3) }];
}

/* Full-page résumé viewer - read the actual document in-app (not just download).
   R98 5A: when the candidate profile carries a real captured résumé (resumeText, from the
   resume-document capture), render THAT verbatim instead of the synthesized demo document. */
function ResumeDocModal({ a, resume, onClose }) {
  const { showToast } = React.useContext(window.E8Ctx);
  const ref = React.useRef(null);
  window.useDialog(true, ref);
  React.useEffect(() => {
    const k = (e) => { if (e.key === 'Escape') { e.preventDefault(); onClose(); } };
    document.addEventListener('keydown', k);
    return () => document.removeEventListener('keydown', k);
  }, [onClose]);
  const slug = (a.name || '').toLowerCase().replace(/[^a-z]+/g, '');
  const realText = String(a.resumeText || '').trim();
  const email = a.email || ((a.name || '').toLowerCase().replace(/[^a-z]+/g, '.') + '@email.com');
  const loc = a.location || ((window.E8DATA.jobs || []).find((j) => j.id === a.jobId) || {}).location || 'Memphis, TN';
  return (
    <div className="e8-overlay" style={{ zIndex: 120 }} onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div ref={ref} className="e8-resume-doc" role="dialog" aria-modal="true" aria-label={a.name + ' résumé'}>
        <div className="e8-resume-bar">
          <span className="e8-sectionlabel">Résumé · {a.name}{realText ? ' · as captured' : ''}</span>
          <span style={{ marginLeft: 'auto', display: 'flex', gap: 6 }}>
            <DSap.Button variant="ghost" size="sm" icon="download" onClick={() => showToast && showToast('Downloading ' + slug + '_resume.pdf (prototype)')}>Download</DSap.Button>
            <DSap.Button variant="ghost" size="sm" icon="close" iconOnly title="Close résumé" onClick={onClose}></DSap.Button>
          </span>
        </div>
        <div className="e8-resume-scroll">
          {realText ? (
            <div className="e8-resume-page">
              <div className="e8-resume-name">{a.name}</div>
              {a.title ? <div className="e8-resume-role">{a.title}</div> : null}
              <div className="e8-resume-contact">{email}{a.phone ? ' · ' + a.phone : ''} · {loc}</div>
              <div className="e8-resume-block">
                <pre className="e8-resume-raw">{realText}</pre>
              </div>
            </div>
          ) : (
          <div className="e8-resume-page">
            <div className="e8-resume-name">{a.name}</div>
            <div className="e8-resume-role">{a.title}</div>
            <div className="e8-resume-contact">{email} · {loc} · linkedin.com/in/{slug}</div>
            <div className="e8-resume-block">
              <div className="e8-resume-bh">Summary</div>
              <p className="e8-resume-p">{resume.summary}</p>
            </div>
            <div className="e8-resume-block">
              <div className="e8-resume-bh">Experience</div>
              {resume.experience.map((e, i) => (
                <div key={i} className="e8-resume-job">
                  <div className="e8-resume-job-top"><span className="e8-resume-job-role">{e.role} · {e.org}</span><span className="tnum e8-resume-job-span">{e.span}</span></div>
                  <ul className="e8-resume-ul">
                    <li>{e.bullet}</li>
                    <li>Partnered cross-functionally and mentored peers on {resume.skills[0]}.</li>
                  </ul>
                </div>
              ))}
            </div>
            <div className="e8-resume-block">
              <div className="e8-resume-bh">Skills</div>
              <p className="e8-resume-p">{resume.skills.join(' · ')}</p>
            </div>
            <div className="e8-resume-block">
              <div className="e8-resume-bh">Education</div>
              <p className="e8-resume-p">{resume.education}</p>
            </div>
          </div>
          )}
        </div>
      </div>
    </div>
  );
}

function ApplicantRejection({ value }) {
  if (!value) return null;
  const decision = window.E8RejectPolicy.normalize(value);
  return (
    <div className={'e8-applicant-rejection' + (decision.restricted ? ' restricted' : '')}>
      <span className="material-symbols-outlined" aria-hidden="true">{decision.restricted ? 'policy' : 'block'}</span>
      <span>{decision.restricted ? 'Restricted review · ' : 'Rejected · '}{decision.reason}</span>
    </div>
  );
}

/* Detail drawer for one applicant: full screen context + résumé, history, notes + qualify/reject. */
function ApplicantDetail({ a, job, onQualify, onReject, onClose }) {
  const { showToast } = React.useContext(window.E8Ctx);
  const resume = buildResume(a, job.title);
  const history = buildHistory(a);
  const [notes, setNotes] = React.useState(() => seedNotes(a));
  const [noteInput, setNoteInput] = React.useState('');
  const [showResume, setShowResume] = React.useState(false);
  const addNote = () => { const t = noteInput.trim(); if (!t) return; setNotes([{ author: 'You', when: 'Just now', text: t }].concat(notes)); setNoteInput(''); if (showToast) showToast('Note added to ' + a.name); };
  const ref = React.useRef(null);
  window.useDialog(true, ref);
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') { e.preventDefault(); onClose(); } };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onClose]);
  return (
    <div className="e8-overlay" onMouseDown={(e) => { if (e.target === e.currentTarget) onClose(); }}>
      <div ref={ref} className="e8-ap-drawer" role="dialog" aria-modal="true" aria-label={'Applicant - ' + a.name}>
        {showResume ? <ResumeDocModal a={a} resume={resume} onClose={() => setShowResume(false)} /> : null}
        <div className="e8-ap-drawer-head">
          <div style={{ minWidth: 0 }}>
            <div className="e8-ap-drawer-name">{a.name}</div>
            <div className="e8-ap-drawer-sub">{a.title} · {a.company}</div>
          </div>
          <DSap.Button variant="ghost" size="sm" icon="close" iconOnly title="Close" onClick={onClose}></DSap.Button>
        </div>
        <div className="e8-ap-drawer-body">
          <div className="e8-ap-drawer-grid">
            <div><div className="e8-sectionlabel">Applied to</div><a className="e8-link" tabIndex={0} onKeyDown={window.keyActivate} onClick={() => navigate('job/' + a.jobId)}>{job.title || a.jobId}{job.client ? ' · ' + job.client : ''}</a></div>
            <div><div className="e8-sectionlabel">Source</div><ApplicantSourceChip source={a.source} dupe={a.dupe} /></div>
            <div><div className="e8-sectionlabel">Applied</div><span style={{ fontSize: 'var(--ui-text-base)' }}>{a.when}</span></div>
            <div><div className="e8-sectionlabel">Owner</div><ActorChip name={a.owner} /></div>
          </div>
          <div className="e8-ap-screen-card">
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 9 }}>
              <span className="e8-sectionlabel">AI screen</span>
              <ApplicantAiCell a={a} />
              <span style={{ marginLeft: 'auto' }}><DSap.ProvenanceBadge kind="advised" /></span>
            </div>
            <ApplicantReasons reasons={a.reasons} />
            {a.referredBy ? <div style={{ fontSize: 'var(--ui-text-sm)', color: 'var(--ui-text-secondary)', marginTop: 8 }}>Referred by <b>{a.referredBy}</b></div> : null}
            <ApplicantRejection value={a.rejectReason} />
          </div>
          <div className="e8-ap-sec">
            <div className="e8-ap-sec-head">
              <span className="e8-sectionlabel">Résumé</span>
              <span className="e8-ap-parsed">{a.resume ? 'parsed on intake' : 'not on file'}</span>
              <span style={{ marginLeft: 'auto', display: 'flex', gap: 6 }}>
                <DSap.Button variant="secondary" size="sm" icon="visibility" onClick={() => setShowResume(true)}>View résumé</DSap.Button>
                <DSap.Button variant="ghost" size="sm" icon="download" iconOnly title="Download PDF" onClick={() => showToast && showToast('Downloading ' + a.name.replace(/\s+/g, '_') + '_resume.pdf (prototype)')}></DSap.Button>
              </span>
            </div>
            <div className="e8-ap-resume">
              <div className="e8-ap-resume-sum">{resume.summary}</div>
              <div className="e8-ap-resume-skills"><DSap.SkillChips skills={resume.skills} max={(resume.skills || []).length} style={{ flexWrap: 'wrap' }} /></div>
              <div className="e8-ap-resume-exp">
                {resume.experience.map((e, i) => (
                  <div key={i} className="e8-ap-exp">
                    <div className="e8-ap-exp-top"><span className="e8-ap-exp-role">{e.role}</span><span className="e8-ap-exp-span tnum">{e.span}</span></div>
                    <div className="e8-ap-exp-org">{e.org}</div>
                    <div className="e8-ap-exp-bullet">{e.bullet}</div>
                  </div>
                ))}
              </div>
              <div className="e8-ap-edu"><span className="material-symbols-outlined" aria-hidden="true">school</span>{resume.education}</div>
            </div>
          </div>

          <div className="e8-ap-sec">
            <div className="e8-ap-sec-head"><span className="e8-sectionlabel">Past applications</span><span className="e8-ap-count tnum">{history.length}</span></div>
            {history.length ? (
              <div className="e8-ap-hist">
                {history.map((hh, i) => (
                  <a key={i} className="e8-ap-histrow" onClick={() => navigate('job/' + hh.jobId)}>
                    <span className="material-symbols-outlined" aria-hidden="true">history</span>
                    <span style={{ flex: 1, minWidth: 0 }}>
                      <span className="e8-ap-histrole">{hh.role}</span>
                      <span className="e8-ap-histsub">{hh.client} · {hh.when}</span>
                    </span>
                    <DSap.Badge tone={/Shortlist/.test(hh.outcome) ? 'success' : /Rejected|passed|Not/.test(hh.outcome) ? 'neutral' : 'warning'}>{hh.outcome}</DSap.Badge>
                  </a>
                ))}
              </div>
            ) : <div className="e8-ap-empty-line">First time applying - no prior applications on file.</div>}
          </div>

          <div className="e8-ap-sec">
            <div className="e8-ap-sec-head"><span className="e8-sectionlabel">Notes</span><span className="e8-ap-count tnum">{notes.length}</span></div>
            {notes.length ? (
              <div className="e8-ap-notes">
                {notes.map((n, i) => (
                  <div key={i} className="e8-ap-note">
                    <div className="e8-ap-note-meta"><ActorChip name={n.author} /><span className="e8-ap-note-when">{n.when}</span></div>
                    <div className="e8-ap-note-text">{n.text}</div>
                  </div>
                ))}
              </div>
            ) : <div className="e8-ap-empty-line">No notes on this candidate yet.</div>}
            <div className="e8-ap-noteadd">
              <input className="e8-ap-noteinput" value={noteInput} placeholder="Add a note…" onChange={(e) => setNoteInput(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') addNote(); }} />
              <DSap.Button variant="secondary" size="sm" icon="add" onClick={addNote} disabled={!noteInput.trim()}>Add</DSap.Button>
            </div>
          </div>
        </div>
        {a.stage === 'New' || a.stage === 'Reviewing' ? (
          <div className="e8-ap-drawer-foot">
            <DSap.Button variant="reject" size="sm" icon="close" onClick={() => onReject(a)}>Reject</DSap.Button>
            <DSap.Button variant="accept" size="sm" icon="how_to_reg" onClick={() => onQualify(a)}>Qualify → pipeline</DSap.Button>
          </div>
        ) : null}
      </div>
    </div>
  );
}

/* Focused one-at-a-time review over the current filter. Swipe / keyboard Q (qualify), R (reject). */
function ApplicantReviewMode({ rows, jobOf, onQualify, onReject, onExit, shortcutsDisabled }) {
  const [idx, setIdx] = React.useState(0);
  const [showResume, setShowResume] = React.useState(false);
  const a = rows[idx];
  const advance = () => setIdx((i) => i + 1);
  React.useEffect(() => {
    const onKey = (e) => {
      const target = e.target;
      const tag = target && target.tagName;
      const isEditing = target && (
        target.isContentEditable ||
        /^(INPUT|TEXTAREA|SELECT)$/.test(tag) ||
        (target.closest && target.closest('[contenteditable="true"]'))
      );
      const isDialog = target && target.closest && target.closest('[role="dialog"]');
      if (showResume || shortcutsDisabled || isEditing || isDialog) return;
      if (e.key === 'q' || e.key === 'Q') { if (a) onQualify(a); }
      else if (e.key === 'r' || e.key === 'R') { if (a) onReject(a); }
      else if (e.key === 'Escape') onExit();
    };
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [a, onQualify, onReject, onExit, shortcutsDisabled, showResume]);
  if (!a) {
    return (
      <div style={{ maxWidth: 460, margin: '48px auto' }}>
        <DSap.EmptyState icon="task_alt" title="Queue cleared" body="You've reviewed every applicant in this filter." cta={<DSap.Button variant="primary" icon="arrow_back" onClick={onExit}>Back to the list</DSap.Button>} />
      </div>
    );
  }
  const job = jobOf(a.jobId);
  const resume = buildResume(a, job.title);
  const history = buildHistory(a);
  return (
    <div className="e8-ap-review">
      {showResume ? <ResumeDocModal a={a} resume={resume} onClose={() => setShowResume(false)} /> : null}
      <div className="e8-ap-review-bar">
        <span style={{ fontSize: 'var(--ui-text-sm)', color: 'var(--ui-text-tertiary)' }}>{idx + 1} of {rows.length}</span>
        <span style={{ marginLeft: 'auto', fontSize: 'var(--ui-text-xs)', color: 'var(--ui-text-tertiary)' }}>Swipe, or press <span className="e8-kbd">Q</span> qualify · <span className="e8-kbd">R</span> reject</span>
        <DSap.Button variant="ghost" size="sm" icon="close" onClick={onExit}>Exit review</DSap.Button>
      </div>
      <window.SwipeTriage key={a.id}
        left={{ label: 'Qualify', icon: 'how_to_reg', bg: 'var(--ui-success)' }}
        right={{ label: 'Reject', icon: 'do_not_disturb_on', bg: 'var(--ui-danger)' }}
        onCommit={(dir) => { if (dir === 'advance') onQualify(a); else onReject(a); }}>
        <div className="e8-ap-rcard">
          <div className="e8-ap-rcard-top">
            <Avatar name={a.name} size="lg" />
            <div style={{ minWidth: 0 }}>
              <div className="e8-ap-rcard-name">{a.name}</div>
              <div className="e8-ap-rcard-sub">{a.title} · {a.company}</div>
            </div>
            <span style={{ marginLeft: 'auto' }}><ApplicantAiCell a={a} /></span>
          </div>
          <div className="e8-ap-rcard-meta">
            <ApplicantSourceChip source={a.source} dupe={a.dupe} />
            <span>·</span>
            <a className="e8-link" tabIndex={0} onKeyDown={window.keyActivate} onClick={() => navigate('job/' + a.jobId)}>{job.title} · {job.client}</a>
            <span style={{ marginLeft: 'auto', color: 'var(--ui-text-tertiary)' }}>{a.when}</span>
          </div>
          <div style={{ fontSize: 'var(--ui-text-sm)', color: 'var(--ui-text-secondary)', lineHeight: 1.45 }}>{resume.summary}</div>
          <div className="e8-ap-resume-skills"><DSap.SkillChips skills={resume.skills.slice(0, 5)} max={5} style={{ flexWrap: 'wrap' }} /></div>
          <div style={{ fontSize: 'var(--ui-text-sm)', color: 'var(--ui-text-tertiary)' }}>{history.length ? 'Past applications: ' + history.length : 'First time applying'}</div>
          <ApplicantReasons reasons={a.reasons} />
        </div>
      </window.SwipeTriage>
      <div className="e8-ap-review-actions">
        <DSap.Button variant="reject" icon="close" onClick={() => onReject(a)}>Reject</DSap.Button>
        <DSap.Button variant="secondary" icon="visibility" onClick={() => setShowResume(true)}>View résumé</DSap.Button>
        <DSap.Button variant="ghost" icon="skip_next" onClick={advance}>Skip</DSap.Button>
        <DSap.Button variant="accept" icon="how_to_reg" onClick={() => onQualify(a)}>Qualify</DSap.Button>
      </div>
    </div>
  );
}

/* The reusable applicant-triage board. Used full-screen (cross-job firehose) and embedded inside
   a Job Order's Applicants tab (scoped via jobId). */
/* R79: the qualify write path, shared by the screen and the 'qualify-applicant' queue
   executor - creating the candidate/match/submission through E8Store (R78 spine). */
/* R104 T5: map an application's source (careers/board/referral/etc.) onto the candidateMeta source
   vocabulary the list Source facet is built from, so a promoted applicant filters cleanly. */
function apSourceToMeta(src) {
  if (src === 'Referral' || src === 'LinkedIn') return src;
  if (src === 'Careers site' || src === 'Inbound' || src === 'Company site') return 'Inbound';
  if (src === 'AI-sourced') return 'AI-sourced';
  return 'Job board'; /* Monster / Indeed / CareerBuilder / other boards */
}
function qualifyApplicantRecord(a) {
  const D = window.E8DATA;
  const j = D.jobs.find((x) => x.id === a.jobId) || {};
  const resume = buildResume(a, j.title);
  const cid = 'c-ap-' + a.id;
  const tier = a.aiScreen === 'Strong' ? 1 : a.aiScreen === 'Weak' ? 3 : 2;
  /* Thread ownership by rep id (not just display name) so the promoted candidate scopes to the
     qualifying rep in the persona-scoped list, mirroring seed candidates (R104 T4). */
  const ownerRep = (D.reps || []).find((r) => r.name === a.owner);

  /* R109 T2: recognize an existing person BEFORE minting a twin. A person is one entity - a second
     application to a second req must attach to the existing record, not create a duplicate. Build a
     matcher draft from the applicant's real signals (name + employer + location + skills, plus
     email/phone/linkedin when the application carried them) and rank it against the candidate corpus. */
  const draft = {
    id: 'ap-' + a.id, name: a.name, company: a.company, lastRole: a.company,
    location: j.location || a.location || 'Memphis, TN', skills: resume.skills,
    email: a.email || '', phone: a.phone || '', linkedin: a.linkedin || a.linkedinUrl || false,
  };
  const ranked = (window.candidateMatches ? window.candidateMatches(draft, { excludeId: draft.id }) : []) || [];
  const top = ranked[0];

  /* High-confidence top match -> AUTO-LINK (policy: auto-link only, never auto-merge). Attach this
     application + a submission to the EXISTING person, write a "Linked to existing candidate" timeline
     note on their record (R104/R106 note), and DO NOT mint a twin. Undo (via the caller) reverses it. */
  if (top && top.band === 'high') {
    const existId = top.id;
    const existName = (D.matches.find((m) => m.id === existId) || {}).name || existId;
    const sid = 's-ap-' + a.id;
    if (j.id && !(D.submissions || []).some((s) => s.id === sid)) {
      window.E8Store.add('submissions', { id: sid, cand: a.name, candId: existId, candTitle: a.title + ' · ' + a.company, job: j.title, jobId: a.jobId, client: j.client, rate: j.rate || '$-', stage: 0, when: 'Just now', owner: a.owner, resp: { label: 'New', tone: 'neutral' }, open: true, stageHistory: [{ stage: 0, at: Date.now(), by: a.owner }] });
    }
    const noteId = 'n-link-' + a.id;
    const sigText = (top.signals || []).map((s) => String(s.label).toLowerCase()).slice(0, 3).join(', ') || 'name and profile';
    if (!(D.notes || []).some((n) => n.id === noteId)) {
      window.E8Store.add('notes', {
        id: noteId, title: 'Linked to existing candidate',
        points: ['New application for ' + (j.title || 'a role') + ' recognized as this person - matched on ' + sigText],
        refType: 'candidate', refId: existId, ts: Date.now(),
        author: a.owner || (D.user || {}).name || 'You', prov: 'human', when: 'Just now',
      });
    }
    window.E8Store.set('applications', a.id, { stage: 'Qualified', linkedCandidateId: existId });
    if (window.E8Audit) window.E8Audit.log({ agent: 'You', prov: 'human', action: 'Linked applicant to existing candidate', target: a.name + ' → ' + existName, route: 'candidate/' + existId });
    if (window.E8Events) window.E8Events.emit('applicant:qualified', { apId: a.id, candId: existId, jobId: a.jobId, linked: true });
    return { linked: true, existId, existName, sid: (j.id ? sid : null), noteId, apId: a.id, prevStage: a.stage, name: a.name, signals: top.signals };
  }

  /* review-band match -> still create the record, but stamp candidateMeta.possibleDuplicateOf so it
     flags on the record + surfaces in the review queue (a human decides, never an auto-merge). */
  const dupIds = (top && top.band === 'review') ? ranked.filter((m) => m.band !== 'low').map((m) => m.id) : [];
  if (!D.matches.some((m) => m.id === cid)) {
    window.E8Store.add('matches', {
      id: cid, jobId: a.jobId, state: 'review', stage: 'prescreen', tier,
      name: a.name, title: a.title, company: a.company, years: resume.years, moreRoles: 1,
      location: j.location || 'Memphis, TN',
      email: a.email || (a.name.toLowerCase().replace(/[^a-z]+/g, '.') + '@email.com'),
      phone: a.phone || ('(901) 555-0' + (100 + (apHash(a.id) % 900))),
      rate: j.rate || '$-', linkedin: true, skills: resume.skills, aiSummary: resume.summary,
      reasons: (a.reasons || []).map((r) => ({ label: r.t, weight: r.s === '+' ? 2 : -1, detail: r.t })),
    });
    window.E8Store.add('candidates', { id: cid, status: 'Screening', source: a.source, availability: 'Immediate', owner: a.owner, ownerId: ownerRep ? ownerRep.id : null, last: 'Just now', lastBy: 'human', lastNote: 'Qualified from Applicants' });
    /* R104 T5: the candidate-list metadata (keyed map). Without this the promoted candidate was
       invisible to the Source/Availability facets, showed `-` for best-match, and lit no verdict.
       bestMatch is the applied job at the screen tier; verdict carries the AI-screen result onto the
       candidate for the list's AI-screen column (candidateMeta.verdict, read by R104 T4). */
    window.E8Store.setMeta(cid, Object.assign({
      source: apSourceToMeta(a.source), availability: 'Immediate', workAuth: 'US Citizen',
      bestMatch: { job: j.title, jobId: a.jobId, client: j.client, tier },
      verdict: a.aiScreen, tags: dupIds.length ? ['Possible duplicate'] : [],
      /* R106: a freshly-qualified applicant entered via a web application and has never been re-verified,
         so it starts the freshness lifecycle at idle/never (entrySource keeps the R104 `source` facet intact). */
      entrySource: 'web-application', verifyState: 'idle', lastVerified: null, verifiedBy: null,
    }, dupIds.length ? { possibleDuplicateOf: dupIds } : {}));
    window.E8Store.add('submissions', { id: 's-ap-' + a.id, cand: a.name, candId: cid, candTitle: a.title + ' · ' + a.company, job: j.title, jobId: a.jobId, client: j.client, rate: j.rate || '$-', stage: 0, when: 'Just now', owner: a.owner, resp: { label: 'New', tone: 'neutral' }, open: true, stageHistory: [{ stage: 0, at: Date.now(), by: a.owner }] });
  }
  window.E8Store.set('applications', a.id, { stage: 'Qualified', linkedCandidateId: cid });
  if (window.E8Audit) window.E8Audit.log({ agent: 'You', prov: 'human', action: 'Qualified applicant', target: a.name + ' → ' + (j.title || 'pipeline'), route: 'candidate/' + cid });
  if (window.E8Events) window.E8Events.emit('applicant:qualified', { apId: a.id, candId: cid, jobId: a.jobId });
  return { cid, sid: 's-ap-' + a.id, apId: a.id, prevStage: a.stage };
}
function unqualifyApplicantRecord(x) {
  /* R109 T2: an auto-linked applicant created no twin - undo just detaches the link (removes the
     submission + timeline note and reverts the application), splitting it back out cleanly. */
  if (x.linked) {
    if (x.sid) window.E8Store.remove('submissions', x.sid);
    if (x.noteId) window.E8Store.remove('notes', x.noteId);
    window.E8Store.set('applications', x.apId, { stage: x.prevStage, linkedCandidateId: null });
    return;
  }
  window.E8Store.remove('matches', x.cid);
  window.E8Store.remove('candidates', x.cid);
  window.E8Store.removeMeta(x.cid);
  window.E8Store.remove('submissions', x.sid);
  window.E8Store.set('applications', x.apId, { stage: x.prevStage, linkedCandidateId: null });
}
if (window.E8Queue) {
  window.E8Queue.registerExecutor('qualify-applicant', {
    run(pl) {
      const a0 = (window.E8DATA.applications || []).find((x) => x.id === pl.apId);
      // Fold in the live screening verdict and skip anything no longer actionable
      // (already qualified, or rejected since this card was queued).
      const a = a0 && Object.assign({}, a0, apScreenOverlay()[a0.id] || {});
      if (a && !a.linkedCandidateId && a.stage !== 'Rejected' && a.stage !== 'Qualified') pl._undo = qualifyApplicantRecord(a);
    },
    undo(pl) { if (pl._undo) { unqualifyApplicantRecord(pl._undo); delete pl._undo; } },
  });
}

/* R79: persisted on-device screening verdicts (score + Strong/Maybe/Weak) per applicant. */
const AP_SCREEN_KEY = 'e8-ap-screen-v1';
function apScreenOverlay() { try { return JSON.parse(localStorage.getItem(AP_SCREEN_KEY)) || {}; } catch (e) { return {}; } }

function ApplicantsBoard({ jobId, initialSegment }) {
  const D = window.E8DATA;
  const { showToast, density } = React.useContext(window.E8Ctx);
  const [segment, setSegment] = React.useState(initialSegment || 'new');
  const segDef = AP_SEGMENTS.find((s) => s.id === segment) || AP_SEGMENTS[0];

  const [apps, setApps] = React.useState(() => { const ov = apScreenOverlay(); return D.applications.filter((a) => !jobId || a.jobId === jobId).map((a) => ({ ...a, ...(ov[a.id] || {}) })); });
  const [sel, setSel] = React.useState([]);
  const [autonomy, setAutonomy] = React.useState('suggest');
  const [fJob, setFJob] = React.useState('all');
  const [fSource, setFSource] = React.useState('all');
  const [fVerdict, setFVerdict] = React.useState('all');
  const [review, setReview] = React.useState(false);
  const [detail, setDetail] = React.useState(null);
  const [rejecting, setRejecting] = React.useState(null); // { ids, label }
  const { confirm, dialog } = window.useConfirm();

  const jobOf = (id) => D.jobs.find((j) => j.id === id) || {};
  const setSeg = (id) => { setSel([]); setReview(false); setSegment(id); };

  const countIn = (stage) => apps.filter((a) => a.stage === stage).length;
  const newCount = countIn('New');
  const strongNew = apps.filter((a) => a.stage === 'New' && a.aiScreen === 'Strong').length;
  const autoArchived = apps.filter((a) => a.autoArchived).length;

  const jobIdsWithApps = Array.from(new Set(apps.map((a) => a.jobId)));
  const sourceList = Array.from(new Set(apps.map((a) => a.source)));
  const segApps = apps.filter((a) => a.stage === segDef.stage);
  const rows = segApps
    .filter((a) => (fJob === 'all' || a.jobId === fJob) && (fSource === 'all' || a.source === fSource) && (fVerdict === 'all' || a.aiScreen === fVerdict))
    .sort((a, b) => (b.score || 0) - (a.score || 0));

  const snap = () => apps.map((a) => ({ ...a }));
  const restore = (s) => () => { setApps(s); setSel([]); };
  const patch = (ids, fields) => setApps((prev) => prev.map((a) => (ids.includes(a.id) ? { ...a, ...fields } : a)));

  // Qualify = promote into the real talent pool: create a Candidate (matches + candidates roster)
  // and a Submission on the applied job. R78: all writes go through E8Store, so the promotion
  // survives reload; Undo reverses the store ops and restores the board.
  const qualify = (ids) => {
    const before = snap();
    const list = apps.filter((a) => ids.includes(a.id));
    const links = {};
    const created = [];
    list.forEach((a) => {
      if (a.linkedCandidateId) { links[a.id] = a.linkedCandidateId; return; }
      const rec = qualifyApplicantRecord(a);
      created.push(rec);
      links[a.id] = rec.linked ? rec.existId : rec.cid;
    });
    setApps((prev) => prev.map((a) => (ids.includes(a.id) ? { ...a, stage: 'Qualified', linkedCandidateId: links[a.id] } : a)));
    setSel([]); setDetail(null);
    const undo = () => {
      created.forEach(unqualifyApplicantRecord);
      setApps(before); setSel([]);
    };
    /* R109 T2: a high-confidence match auto-linked the application onto an existing person (no twin) -
       say so, otherwise the standard "qualified into pipeline" copy. */
    const linkedRecs = created.filter((r) => r.linked);
    const msg = ids.length === 1
      ? (created[0] && created[0].linked
          ? 'Recognized ' + list[0].name + ' - linked to their existing profile'
          : list[0].name + ' qualified - now in Candidates + ' + (jobOf(list[0].jobId).title || 'the') + ' pipeline')
      : ids.length + ' applicants qualified' + (linkedRecs.length ? ' (' + linkedRecs.length + ' linked to existing profiles)' : '') + ' - added to Candidates + their pipelines';
    showToast(msg, 'Undo', undo);
  };
  const doReject = (ids, value) => {
    const before = snap();
    const list = apps.filter((a) => ids.includes(a.id));
    const decision = window.E8RejectPolicy.normalize(value || 'Not a fit');
    patch(ids, { stage: 'Rejected', rejectReason: decision });
    list.forEach((a) => {
      window.E8Store.set('applications', a.id, { stage: 'Rejected', rejectReason: decision });
      if (window.E8Audit) window.E8Audit.log({
        agent: 'You',
        prov: 'human',
        action: 'Rejected applicant · ' + decision.category + ' · ' + decision.reason,
        target: a.name,
        route: 'applicants',
        category: decision.category,
        reason: decision.reason,
        restricted: decision.restricted,
      });
    });
    setSel([]); setDetail(null); setRejecting(null);
    const msg = ids.length === 1 ? list[0].name + ' rejected - ' + window.E8RejectPolicy.summaryLabel(decision) : ids.length + ' applicants rejected';
    showToast(msg, 'Undo', () => { list.forEach((a) => window.E8Store.set('applications', a.id, { stage: a.stage, rejectReason: a.rejectReason || null })); restore(before)(); });
  };
  const askReject = (ids, label) => setRejecting({ ids, label });
  const moveToReviewing = (ids) => {
    const before = snap();
    const list = apps.filter((a) => ids.includes(a.id));
    patch(ids, { stage: 'Reviewing', rejectReason: null });
    list.forEach((a) => window.E8Store.set('applications', a.id, { stage: 'Reviewing', rejectReason: null }));
    setSel([]);
    showToast((ids.length === 1 ? 'Moved to Reviewing' : ids.length + ' moved to Reviewing'), 'Undo', () => {
      list.forEach((a) => window.E8Store.set('applications', a.id, { stage: a.stage, rejectReason: a.rejectReason || null }));
      restore(before)();
    });
  };
  const assignMe = (ids) => { const before = snap(); patch(ids, { owner: D.user.name }); setSel([]); showToast((ids.length === 1 ? 'Assigned to you' : ids.length + ' assigned to you'), 'Undo', restore(before)); };

  const setAutonomyLevel = (lvl) => {
    setAutonomy(lvl);
    if (lvl === 'draft') {
      setSel(apps.filter((a) => a.stage === 'New' && (a.knockout || a.aiScreen === 'Weak')).map((a) => a.id));
      /* R79: Draft also queues every Strong new applicant into Approvals - one tap there
         runs the real qualify executor (candidate + submission created). */
      const strong = apps.filter((a) => a.stage === 'New' && a.aiScreen === 'Strong' && !a.knockout && !a.linkedCandidateId);
      const queued = window.E8Queue.list();
      let n = 0;
      strong.forEach((a) => {
        const dupe = queued.some((q) => q.action && q.action.type === 'qualify-applicant' && q.action.payload.apId === a.id && q.state === 'review');
        if (dupe) return;
        const j = jobOf(a.jobId);
        window.E8Queue.enqueue({
          kind: 'Qualify', icon: 'how_to_reg', agent: 'Intake', prov: 'drafted', conf: 'high',
          title: 'Qualify ' + a.name + ' → ' + (j.title || a.jobId),
          detail: (a.note || 'Strong AI screen') + ' · Approving creates the candidate record + a submission on ' + (j.title || a.jobId) + '.',
          route: 'applicants', primary: 'Qualify',
          action: { type: 'qualify-applicant', payload: { apId: a.id } },
        });
        n++;
      });
      showToast(n ? 'Knockouts pre-selected + ' + n + ' strong applicant' + (n > 1 ? 's' : '') + ' queued in Approvals' : 'AI pre-selected likely knockouts - review, then reject in one click', n ? 'Open Approvals' : undefined, n ? () => navigate('approvals') : undefined);
    } else if (lvl === 'auto') {
      /* R79: Auto qualifies every Strong new applicant outright - reversible, as always. */
      const strong = apps.filter((a) => a.stage === 'New' && a.aiScreen === 'Strong' && !a.knockout && !a.linkedCandidateId);
      if (strong.length) qualify(strong.map((a) => a.id));
      const targets = apps.filter((a) => a.stage === 'New' && (a.knockout || a.aiScreen === 'Weak'));
      if (targets.length) {
        const ids = targets.map((t) => t.id);
        const autoDecision = window.E8RejectPolicy.record('role', 'Skills gap', 'Auto-screened knockout criteria');
        patch(ids, { stage: 'Rejected', rejectReason: autoDecision, autoArchived: true });
        targets.forEach((a) => window.E8Store.set('applications', a.id, { stage: 'Rejected', rejectReason: autoDecision }));
        setSel([]);
        /* Targeted undo (not a whole-board snapshot) - qualify() may have just promoted other rows. */
        showToast('Auto-archived ' + ids.length + ' clear knockouts', 'Undo', () => {
          patch(ids, { stage: 'New', rejectReason: undefined, autoArchived: undefined });
          targets.forEach((a) => window.E8Store.set('applications', a.id, { stage: 'New', rejectReason: null }));
          setSel([]);
        });
      } else { showToast('No clear knockouts to auto-archive right now'); }
    }
  };

  /* R79: real screening - band each visible applicant's on-device fit score into
     Strong/Maybe/Weak (knockouts stay Weak deterministically). Persists to e8-ap-screen-v1. */
  const [screening, setScreening] = React.useState(false);
  const [screenDetail, setScreenDetail] = React.useState('');
  const reScreen = async () => {
    if (screening) return;
    const targets = rows.filter((a) => a.stage === 'New' || a.stage === 'Reviewing').slice(0, 24);
    if (!targets.length) { showToast('No applicants in view to screen'); return; }
    setScreening(true);
    try {
      if (!window.E8AI.getState().enabled) window.E8AI.enable();
      const ov = apScreenOverlay();
      for (let i = 0; i < targets.length; i++) {
        const a = targets[i];
        setScreenDetail('Screening ' + a.name + ' (' + (i + 1) + '/' + targets.length + ')');
        const job = jobOf(a.jobId);
        const res = await window.E8Match.scoreOne(job, { id: a.id, skills: buildResume(a, a.title).skills, title: a.title, company: a.company, summary: a.note || '', location: '', rate: '' });
        const verdict = a.knockout ? 'Weak' : res.score >= 72 ? 'Strong' : res.score >= 50 ? 'Maybe' : 'Weak';
        ov[a.id] = { score: res.score, aiScreen: verdict, screenedAt: new Date().toISOString() };
      }
      try { localStorage.setItem(AP_SCREEN_KEY, JSON.stringify(ov)); } catch (e) {}
      setApps((prev) => prev.map((a) => (ov[a.id] ? { ...a, ...ov[a.id] } : a)));
      if (window.E8Audit) window.E8Audit.log({ agent: 'Intake', prov: 'advised', action: 'Screened ' + targets.length + ' applicants on-device', target: 'verdicts banded from live fit scores', route: 'applicants' });
      showToast(targets.length + ' applicants screened on-device - verdicts updated');
    } catch (e) { showToast('On-device screening unavailable in this browser'); }
    finally { setScreening(false); setScreenDetail(''); }
  };

  const registry = [
    { key: 'name', label: 'Applicant', locked: true, sortable: true, sortValue: (r) => r.name, render: (r) => <NameCell name={r.name} sub={r.title + ' · ' + r.company} /> },
    { key: 'job', label: 'Applied to', sortable: true, sortValue: (r) => jobOf(r.jobId).title || r.jobId, render: (r) => { const j = jobOf(r.jobId); return (
      <a className="e8-link" tabIndex={0} onKeyDown={window.keyActivate} onClick={(e) => { e.stopPropagation(); navigate('job/' + r.jobId); }} style={{ display: 'inline-block', lineHeight: 1.3 }}>
        <span style={{ fontWeight: 500 }}>{j.title || r.jobId}</span>
        <span style={{ display: 'block', fontSize: 'var(--ui-text-xs)', color: 'var(--ui-text-tertiary)' }}>{j.client || ''}</span>
      </a>
    ); } },
    { key: 'source', label: 'Source', sortable: true, sortValue: (r) => r.source, render: (r) => <ApplicantSourceChip source={r.source} dupe={r.dupe} /> },
    { key: 'ai', label: 'AI screen', icon: 'auto_awesome', sortable: true, sortValue: (r) => (r.score == null ? -1 : r.score), render: (r) => <ApplicantAiCell a={r} /> },
    { key: 'when', label: 'Applied', secondary: true, render: (r) => r.when },
    { key: 'owner', label: 'Owner', sortable: true, sortValue: (r) => r.owner, render: (r) => <ActorChip name={r.owner} /> },
  ].filter((c) => !(jobId && c.key === 'job'));

  const rowActions = (r) => (segDef.stage === 'New' || segDef.stage === 'Reviewing') ? (
    <React.Fragment>
      <DSap.Button variant="accept" size="sm" icon="how_to_reg" onClick={() => qualify([r.id])}>Qualify</DSap.Button>
      <DSap.Button variant="reject" size="sm" icon="close" onClick={() => askReject([r.id], r.name)}>Reject</DSap.Button>
    </React.Fragment>
  ) : (segDef.stage === 'Rejected'
    ? <DSap.Button variant="ghost" size="sm" icon="undo" onClick={() => moveToReviewing([r.id])}>Reopen</DSap.Button>
    : <DSap.Button variant="ghost" size="sm" icon="open_in_new" onClick={() => { if (r.linkedCandidateId) navigate('candidate/' + r.linkedCandidateId); else showToast('No linked candidate record in the demo data'); }}>View candidate</DSap.Button>);

  const emptyFor = () => {
    const filtered = fJob !== 'all' || fSource !== 'all' || fVerdict !== 'all';
    if (filtered) return <div style={{ maxWidth: 460, margin: '40px auto' }}><DSap.EmptyState icon="filter_alt_off" title="No applicants match" body="Nothing in this view matches the current filters." cta={<DSap.Button variant="secondary" icon="filter_alt_off" onClick={() => { setFJob('all'); setFSource('all'); setFVerdict('all'); }}>Clear filters</DSap.Button>} /></div>;
    const copy = {
      New: { icon: 'move_to_inbox', title: 'Inbox zero', body: 'No new applicants to triage. New applications from your job ads land here.' },
      Reviewing: { icon: 'fact_check', title: 'Nothing in review', body: 'Applicants you move to Reviewing while you screen them show up here.' },
      Qualified: { icon: 'how_to_reg', title: 'No one qualified yet', body: 'Qualified applicants get promoted into Candidates and their job pipeline.' },
      Rejected: { icon: 'do_not_disturb_on', title: 'Nothing rejected', body: 'Rejected and auto-archived applicants are kept here - never deleted.' },
    }[segDef.stage];
    return <div style={{ maxWidth: 460, margin: '40px auto' }}><DSap.EmptyState icon={copy.icon} title={copy.title} body={copy.body} /></div>;
  };

  const filterBar = (
    <div className="e8-ap-filters">
      {!jobId ? (
        <DSap.Select value={fJob} onChange={(e) => setFJob(e.target.value)} aria-label="Filter by job order"
          options={[{ value: 'all', label: 'All jobs' }].concat(jobIdsWithApps.map((id) => ({ value: id, label: jobOf(id).title || id })))} />
      ) : null}
      <DSap.Select value={fSource} onChange={(e) => setFSource(e.target.value)} aria-label="Filter by source"
        options={[{ value: 'all', label: 'All sources' }].concat(sourceList.map((s) => ({ value: s, label: s })))} />
      <DSap.Select value={fVerdict} onChange={(e) => setFVerdict(e.target.value)} aria-label="Filter by AI screen"
        options={[
          { value: 'all', label: 'Any AI screen' },
          { value: 'Strong', label: 'Strong' },
          { value: 'Maybe', label: 'Maybe' },
          { value: 'Weak', label: 'Weak' },
          { value: 'Pending', label: 'Pending' },
        ]} />
    </div>
  );

  const headRow = (
    <div className="e8-ap-head">
      <div className="e8-ap-stats">
        <b className="tnum">{newCount}</b> new · <b className="tnum">{strongNew}</b> strong{autoArchived ? <span> · <span className="tnum">{autoArchived}</span> auto-archived</span> : null}
        <span className="e8-ap-prov"><DSap.ProvenanceBadge kind="advised" /> AI screens are advisory - you decide</span>
      </div>
      <div className="e8-ap-head-right">
        <span className="e8-ap-autonomy" role="group" aria-label="AI autonomy">
          <span className="e8-ap-autonomy-lab" title="How much the AI does before you look">AI</span>
          {AP_AUTONOMY.map((o) => (
            <button key={o.id} type="button" className={'e8-ap-auton-btn' + (autonomy === o.id ? ' on' : '')} aria-pressed={autonomy === o.id} onClick={() => setAutonomyLevel(o.id)}>{o.label}</button>
          ))}
        </span>
        <DSap.Button variant="secondary" size="sm" icon={screening ? 'progress_activity' : 'auto_awesome'} onClick={reScreen} disabled={screening || (segDef.stage !== 'New' && segDef.stage !== 'Reviewing')} title="Score every visible applicant against their job with the on-device fit engine">{screening ? (screenDetail || 'Screening…') : 'Screen on-device'}</DSap.Button>
        <DSap.Button variant="secondary" size="sm" icon="style" onClick={() => setReview(true)} disabled={!rows.length || (segDef.stage !== 'New' && segDef.stage !== 'Reviewing')}>Review mode</DSap.Button>
      </div>
    </div>
  );

  return (
    <React.Fragment>
      {review ? (
        <ApplicantReviewMode rows={rows} jobOf={jobOf}
          onQualify={(a) => qualify([a.id])}
          onReject={(a) => askReject([a.id], a.name)}
          onExit={() => setReview(false)}
          shortcutsDisabled={!!rejecting} />
      ) : (
        <React.Fragment>
          {headRow}
          <div className="e8-ap-toolbar">
            <DSap.SubTabs
              items={AP_SEGMENTS.map((s) => ({ id: s.id, label: s.label, count: countIn(s.stage) }))}
              active={segDef.id}
              onChange={setSeg}
            />
            {filterBar}
          </div>
          <div style={{ position: 'relative' }}>
            <ConfigurableTable
              listKey={jobId ? 'applicants-job' : 'applicants'}
              registry={registry}
              rows={rows}
              density={density}
              selectable={segDef.stage === 'New' || segDef.stage === 'Reviewing'}
              selected={sel}
              onSelect={setSel}
              onRowClick={(r) => setDetail(r)}
              rowActions={rowActions}
              empty={emptyFor()}
              viewState={{ segment, fJob, fSource, fVerdict }}
              onApplyViewState={(state) => {
                setSel([]);
                setReview(false);
                setDetail(null);
                if (state.segment && AP_SEGMENTS.some((item) => item.id === state.segment)) setSegment(state.segment);
                if (typeof state.fJob === 'string') setFJob(state.fJob);
                if (typeof state.fSource === 'string') setFSource(state.fSource);
                if (typeof state.fVerdict === 'string') setFVerdict(state.fVerdict);
              }}
            />
            <DSap.BulkBar
              count={sel.length}
              actions={[
                { icon: 'how_to_reg', label: 'Qualify', onClick: () => qualify(sel) },
                { icon: 'close', label: 'Reject', onClick: () => askReject(sel, sel.length + ' applicants') },
                { icon: 'fact_check', label: 'Move to review', onClick: () => moveToReviewing(sel) },
                { icon: 'person_add', label: 'Assign to me', onClick: () => assignMe(sel) },
              ]}
              onClear={() => setSel([])}
            />
          </div>
        </React.Fragment>
      )}

      {detail ? <ApplicantDetail a={detail} job={jobOf(detail.jobId)} onQualify={(a) => qualify([a.id])} onReject={(a) => askReject([a.id], a.name)} onClose={() => setDetail(null)} /> : null}
      {rejecting ? <window.RejectReasonDialog name={rejecting.label} onConfirm={(payload) => doReject(rejecting.ids, payload)} onClose={() => setRejecting(null)} /> : null}
      {dialog}
    </React.Fragment>
  );
}

/* Full-screen cross-job firehose. */
function ApplicantsScreen({ segment }) {
  return (
    <React.Fragment>
      <Topbar crumbs={[{ label: 'Applicants' }]} />
      <div className="e8-content">
        <div className="e8-page">
          <ApplicantsBoard initialSegment={segment} />
        </div>
      </div>
    </React.Fragment>
  );
}

Object.assign(window, { ApplicantsScreen, ApplicantsBoard, ResumeDocModal });
