/* Mereka v2 — Unified Participant / Talent Data Management (Talent Corp)
   Spec: MRK-14468. This prototype covers the user-facing half of the spec:
   the canonical TalentProfile shape, the admin directory + filters, the
   consent gate, tokenised partner sharing with QR, and share analytics.
   Everything persists in localStorage so consent, shares and view counts
   survive a reload and behave the same across pages — the real thing is a
   V2 backend (see the API in §6 of the spec); nothing here is a server. */

/* ---------- Canonical vocabulary ---------- */
const TALENT_PROGRAMS = ['YH-MTA', 'IBM-DTA', 'Microsoft', 'CIMB-TVET', 'NextGenSellers', 'MyFutureJobs'];
const TALENT_STATUSES = ['in_progress', 'completed', 'certified'];
const TALENT_AVAILABILITY = ['available', 'placed', 'unavailable'];
const TALENT_INSTITUTIONS = ['Universiti Malaya', 'UiTM Shah Alam', 'Politeknik Sultan Salahuddin', 'UTM Skudai', 'Kolej Komuniti Selayang', 'Universiti Sains Malaysia', 'German-Malaysian Institute', 'Universiti Teknologi MARA Penang'];
const TALENT_LOCATIONS = ['Kuala Lumpur', 'Selangor', 'Penang', 'Johor', 'Sarawak', 'Sabah', 'Perak'];
const TALENT_SKILLS = ['Data Analysis', 'Python', 'Power BI', 'Digital Marketing', 'Cloud Fundamentals', 'AI Literacy', 'Prompt Engineering', 'Customer Service', 'E-commerce Ops', 'Cybersecurity', 'Networking', 'CNC Machining', 'Quality Control', 'Copywriting', 'Sales', 'Excel', 'UI Design', 'Project Coordination'];

/* Source systems and how each one's columns land on the canonical schema.
   This is EPIC B1's field-mapping doc, rendered rather than written down, so
   the mapping a program owner has to confirm is visible in the product. */
const TALENT_SOURCES = [
  { id: 'YH-MTA', label: 'YH-MTA cohort sheet', kind: 'Google Sheets', owner: 'Program PM', rows: 42,
    map: [['Full Name', 'full_name'], ['Email Address', 'email'], ['Mobile', 'phone'], ['IC (hashed on import)', 'national_id_hash'], ['University', 'institution'], ['State', 'location'], ['Cohort', 'program_cohort'], ['Status', 'completion_status'], ['Cert Issued', 'certifications[]']] },
  { id: 'IBM-DTA', label: 'IBM-DTA programme DB', kind: 'Database export', owner: 'Fabsya / IBM', rows: 28, unconfirmed: true,
    map: [['participant_name', 'full_name'], ['contact_email', 'email'], ['contact_no', 'phone'], ['institution_name', 'institution'], ['badge_list', 'certifications[]'], ['completion', 'completion_status']] },
  { id: 'Microsoft', label: 'Microsoft programme sheet', kind: 'Google Sheets', owner: 'Program PM', rows: 36,
    map: [['Name', 'full_name'], ['Email', 'email'], ['Phone', 'phone'], ['Institution', 'institution'], ['Location', 'location'], ['Skills (comma sep)', 'skills[]'], ['Certification', 'certifications[]']] },
  { id: 'CIMB-TVET', label: 'CIMB TVET sheet', kind: 'Google Sheets', owner: 'Program PM', rows: 31,
    map: [['Nama Penuh', 'full_name'], ['Emel', 'email'], ['No. Telefon', 'phone'], ['Institusi', 'institution'], ['Negeri', 'location'], ['Sijil', 'certifications[]']] },
  { id: 'NextGenSellers', label: 'NextGen Sellers sheet', kind: 'Google Sheets', owner: 'Program PM', rows: 24,
    map: [['Seller Name', 'full_name'], ['Email', 'email'], ['WhatsApp', 'phone'], ['Region', 'location'], ['Track', 'skills[]'], ['Graduated', 'completion_status']] },
  { id: 'MyFutureJobs', label: 'MyFutureJobs onboarding (SharePoint)', kind: 'SharePoint / Excel', owner: 'Puteri', rows: 57,
    map: [['Candidate', 'full_name'], ['Email', 'email'], ['Contact', 'phone'], ['Available From', 'availability_date'], ['Placement Status', 'availability_status'], ['Institution', 'institution']] },
];

/* ---------- Mock canonical profiles ----------
   One record per person. Profiles carrying more than one source_record are the
   merge cases (spec AC2): the same person arrived from two programmes and was
   matched on email or phone. consent is deliberately mixed so the share gate
   has something to hide. */
function _tp(id, name, email, phone, inst, loc, prog, cohort, status, skills, certs, availStatus, availDate, consent, sources, extra) {
  return Object.assign({
    id, full_name: name, email, phone,
    national_id_hash: 'sha256:' + _hashStr(email + '|ic').toString(16).padStart(12, '0'),
    institution: inst, location: loc, current_program: prog, program_cohort: cohort,
    completion_status: status, skills,
    certifications: certs, latest_certification: certs.length ? certs[certs.length - 1].name : '',
    availability_status: availStatus, availability_date: availDate,
    consent_share_employers: consent, consent_ts: consent ? '2026-09-12T09:24:00+08:00' : null,
    source_records: sources, created_at: '2026-06-04', updated_at: '2026-09-21',
  }, extra || {});
}
const TALENT_PROFILES = [
  _tp('t01', 'Nur Aisyah binti Rahman', 'aisyah.rahman@example.com', '+60123456701', 'Universiti Malaya', 'Kuala Lumpur', 'Microsoft', 'MS-2026-A', 'certified',
    ['AI Literacy', 'Prompt Engineering', 'Data Analysis'],
    [{ name: 'Microsoft AI Fluency', issued_at: '2026-08-14', url: '/app/certificate/ai-fluency' }, { name: 'Mereka AI4U Completion', issued_at: '2026-09-02', url: '/app/certificate/ai4u' }],
    'available', '2026-10-01', true,
    [{ source_system: 'Microsoft', source_record_ref: 'MS-row-118', matched_on: 'seed' }, { source_system: 'YH-MTA', source_record_ref: 'YH-row-22', matched_on: 'email' }]),
  _tp('t02', 'Muhammad Danial bin Zainal', 'danial.zainal@example.com', '+60123456702', 'Politeknik Sultan Salahuddin', 'Selangor', 'CIMB-TVET', 'TVET-06', 'certified',
    ['CNC Machining', 'Quality Control'],
    [{ name: 'CIMB TVET Level 3', issued_at: '2026-07-30', url: '#' }],
    'available', '2026-10-15', true,
    [{ source_system: 'CIMB-TVET', source_record_ref: 'CIMB-row-14', matched_on: 'seed' }]),
  _tp('t03', 'Kavitha a/p Suresh', 'kavitha.suresh@example.com', '+60123456703', 'UiTM Shah Alam', 'Selangor', 'YH-MTA', 'YH-2026-2', 'completed',
    ['Digital Marketing', 'Copywriting', 'E-commerce Ops'],
    [{ name: 'YH-MTA Digital Marketing', issued_at: '2026-08-21', url: '#' }],
    'available', '2026-09-30', true,
    [{ source_system: 'YH-MTA', source_record_ref: 'YH-row-08', matched_on: 'seed' }, { source_system: 'NextGenSellers', source_record_ref: 'NGS-row-31', matched_on: 'phone' }]),
  _tp('t04', 'Lim Wei Sheng', 'lim.weisheng@example.com', '+60123456704', 'UTM Skudai', 'Johor', 'IBM-DTA', 'DTA-3', 'certified',
    ['Python', 'Data Analysis', 'Cloud Fundamentals'],
    [{ name: 'IBM Data Analyst Badge', issued_at: '2026-06-18', url: '#' }, { name: 'IBM Cloud Essentials', issued_at: '2026-08-04', url: '#' }],
    'placed', '2026-08-01', true,
    [{ source_system: 'IBM-DTA', source_record_ref: 'IBM-1042', matched_on: 'seed' }]),
  _tp('t05', 'Siti Zubaidah binti Omar', 'siti.zubaidah@example.com', '+60123456705', 'Kolej Komuniti Selayang', 'Selangor', 'MyFutureJobs', '—', 'in_progress',
    ['Customer Service', 'Excel'],
    [],
    'available', '2026-11-01', false,
    [{ source_system: 'MyFutureJobs', source_record_ref: 'MFJ-row-77', matched_on: 'seed' }]),
  _tp('t06', 'Arjun Nair', 'arjun.nair@example.com', '+60123456706', 'Universiti Sains Malaysia', 'Penang', 'Microsoft', 'MS-2026-A', 'certified',
    ['Cloud Fundamentals', 'Cybersecurity', 'Networking'],
    [{ name: 'Microsoft Azure Fundamentals', issued_at: '2026-07-11', url: '#' }],
    'available', '2026-10-06', true,
    [{ source_system: 'Microsoft', source_record_ref: 'MS-row-090', matched_on: 'seed' }]),
  _tp('t07', 'Farah Nabila binti Yusof', 'farah.nabila@example.com', '+60123456707', 'Universiti Malaya', 'Kuala Lumpur', 'YH-MTA', 'YH-2026-2', 'certified',
    ['UI Design', 'Project Coordination', 'AI Literacy'],
    [{ name: 'YH-MTA Design Track', issued_at: '2026-08-21', url: '#' }],
    'available', '2026-10-01', true,
    [{ source_system: 'YH-MTA', source_record_ref: 'YH-row-11', matched_on: 'seed' }]),
  _tp('t08', 'Tan Mei Ling', 'tan.meiling@example.com', '+60123456708', 'German-Malaysian Institute', 'Selangor', 'CIMB-TVET', 'TVET-06', 'completed',
    ['Quality Control', 'Excel'],
    [{ name: 'CIMB TVET Level 2', issued_at: '2026-05-29', url: '#' }],
    'unavailable', '2027-01-05', false,
    [{ source_system: 'CIMB-TVET', source_record_ref: 'CIMB-row-02', matched_on: 'seed' }]),
  _tp('t09', 'Mohd Hafiz bin Ismail', 'hafiz.ismail@example.com', '+60123456709', 'Politeknik Sultan Salahuddin', 'Perak', 'NextGenSellers', 'NGS-Q3', 'certified',
    ['E-commerce Ops', 'Sales', 'Digital Marketing'],
    [{ name: 'NextGen Seller Certified', issued_at: '2026-09-05', url: '#' }],
    'available', '2026-09-29', true,
    [{ source_system: 'NextGenSellers', source_record_ref: 'NGS-row-04', matched_on: 'seed' }, { source_system: 'MyFutureJobs', source_record_ref: 'MFJ-row-12', matched_on: 'email' }]),
  _tp('t10', 'Chong Kar Wai', 'chong.karwai@example.com', '+60123456710', 'UTM Skudai', 'Johor', 'IBM-DTA', 'DTA-3', 'in_progress',
    ['Python', 'Power BI'],
    [],
    'available', '2026-12-01', true,
    [{ source_system: 'IBM-DTA', source_record_ref: 'IBM-1090', matched_on: 'seed' }]),
  _tp('t11', 'Nurul Izzati binti Kamal', 'nurul.izzati@example.com', '+60123456711', 'Universiti Teknologi MARA Penang', 'Penang', 'Microsoft', 'MS-2026-B', 'certified',
    ['AI Literacy', 'Power BI', 'Data Analysis'],
    [{ name: 'Microsoft Data Fundamentals', issued_at: '2026-09-09', url: '#' }],
    'available', '2026-10-20', true,
    [{ source_system: 'Microsoft', source_record_ref: 'MS-row-141', matched_on: 'seed' }]),
  _tp('t12', 'Jason Anak Belaja', 'jason.belaja@example.com', '+60123456712', 'Universiti Malaya', 'Sarawak', 'YH-MTA', 'YH-2026-1', 'completed',
    ['Project Coordination', 'Copywriting'],
    [{ name: 'YH-MTA Foundation', issued_at: '2026-04-16', url: '#' }],
    'placed', '2026-06-01', false,
    [{ source_system: 'YH-MTA', source_record_ref: 'YH-row-03', matched_on: 'seed' }]),
  _tp('t13', 'Aina Sofea binti Hamzah', 'aina.sofea@example.com', '+60123456713', 'Kolej Komuniti Selayang', 'Kuala Lumpur', 'MyFutureJobs', '—', 'completed',
    ['Customer Service', 'Sales'],
    [{ name: 'MyFutureJobs Readiness', issued_at: '2026-08-28', url: '#' }],
    'available', '2026-10-01', true,
    [{ source_system: 'MyFutureJobs', source_record_ref: 'MFJ-row-45', matched_on: 'seed' }]),
  _tp('t14', 'Ravi Chandran', 'ravi.chandran@example.com', '+60123456714', 'Universiti Sains Malaysia', 'Penang', 'IBM-DTA', 'DTA-2', 'certified',
    ['Python', 'Cybersecurity', 'Cloud Fundamentals'],
    [{ name: 'IBM Cybersecurity Analyst', issued_at: '2026-07-22', url: '#' }],
    'available', '2026-10-10', true,
    [{ source_system: 'IBM-DTA', source_record_ref: 'IBM-0988', matched_on: 'seed' }]),
  _tp('t15', 'Wan Nur Syafiqah', 'wan.syafiqah@example.com', '+60123456715', 'UiTM Shah Alam', 'Selangor', 'NextGenSellers', 'NGS-Q3', 'in_progress',
    ['E-commerce Ops', 'Digital Marketing'],
    [],
    'available', '2026-11-15', false,
    [{ source_system: 'NextGenSellers', source_record_ref: 'NGS-row-19', matched_on: 'seed' }]),
  _tp('t16', 'Gopal Krishnan', 'gopal.krishnan@example.com', '+60123456716', 'German-Malaysian Institute', 'Selangor', 'CIMB-TVET', 'TVET-05', 'certified',
    ['CNC Machining', 'Quality Control', 'Excel'],
    [{ name: 'CIMB TVET Level 3', issued_at: '2026-06-30', url: '#' }],
    'available', '2026-09-29', true,
    [{ source_system: 'CIMB-TVET', source_record_ref: 'CIMB-row-27', matched_on: 'seed' }]),
  _tp('t17', 'Nadia Iskandar', 'nadia.iskandar@example.com', '+60123456717', 'Universiti Malaya', 'Kuala Lumpur', 'Microsoft', 'MS-2026-B', 'completed',
    ['Prompt Engineering', 'AI Literacy', 'UI Design'],
    [{ name: 'Microsoft AI Fluency', issued_at: '2026-09-15', url: '/app/certificate/ai-fluency' }],
    'available', '2026-10-05', true,
    [{ source_system: 'Microsoft', source_record_ref: 'MS-row-160', matched_on: 'seed' }]),
  _tp('t18', 'Adam Harith bin Roslan', 'adam.harith@example.com', '+60123456718', 'Politeknik Sultan Salahuddin', 'Sabah', 'YH-MTA', 'YH-2026-2', 'in_progress',
    ['Networking', 'Cloud Fundamentals'],
    [],
    'unavailable', '2027-02-01', false,
    [{ source_system: 'YH-MTA', source_record_ref: 'YH-row-40', matched_on: 'seed' }]),
  _tp('t19', 'Sharifah Aleya', 'sharifah.aleya@example.com', '+60123456719', 'Universiti Teknologi MARA Penang', 'Penang', 'MyFutureJobs', '—', 'completed',
    ['Excel', 'Project Coordination', 'Customer Service'],
    [{ name: 'MyFutureJobs Readiness', issued_at: '2026-09-01', url: '#' }],
    'available', '2026-10-01', true,
    [{ source_system: 'MyFutureJobs', source_record_ref: 'MFJ-row-51', matched_on: 'seed' }, { source_system: 'CIMB-TVET', source_record_ref: 'CIMB-row-33', matched_on: 'national_id_hash' }]),
  _tp('t20', 'Teh Jia Xin', 'teh.jiaxin@example.com', '+60123456720', 'UTM Skudai', 'Johor', 'NextGenSellers', 'NGS-Q2', 'certified',
    ['Sales', 'E-commerce Ops', 'Copywriting'],
    [{ name: 'NextGen Seller Certified', issued_at: '2026-06-12', url: '#' }],
    'placed', '2026-07-01', true,
    [{ source_system: 'NextGenSellers', source_record_ref: 'NGS-row-07', matched_on: 'seed' }]),
];

/* ---------- Prototype state ----------
   Consent changes, share links and view events live in localStorage so the
   learner toggle, the admin directory and a partner link all read the same
   truth across pages and reloads. In V2 these are the consent column, the
   shares table and the share_events table behind the §6 API. */
const _TAL_KEY = 'mereka_talent_v1';
let _talState = (() => {
  try { return JSON.parse(localStorage.getItem(_TAL_KEY) || '{}'); } catch (e) { return {}; }
})();
const _talSubs = new Set();
function _talSave() {
  try { localStorage.setItem(_TAL_KEY, JSON.stringify(_talState)); } catch (e) {}
  _talSubs.forEach(fn => fn());
}
function useTalentState() {
  const [, bump] = React.useState(0);
  React.useEffect(() => { const fn = () => bump(n => n + 1); _talSubs.add(fn); return () => _talSubs.delete(fn); }, []);
  return _talState;
}
/* Consent: the seed value is the profile's, an explicit learner toggle wins. */
function talentConsent(p) {
  const o = (_talState.consent || {})[p.id];
  return o === undefined ? !!p.consent_share_employers : !!o;
}
function setTalentConsent(id, v) {
  _talState.consent = Object.assign({}, _talState.consent, { [id]: !!v });
  _talState.consentTs = Object.assign({}, _talState.consentTs, { [id]: new Date().toISOString() });
  _talSave();
}
function talentConsentTs(p) {
  return (_talState.consentTs || {})[p.id] || p.consent_ts;
}
/* Imports: remember which sources have been run so a second run can honestly
   report "0 created, N updated" instead of pretending to insert again (AC1). */
function talentImportRuns() { return _talState.runs || []; }
function recordTalentImport(run) { _talState.runs = [run].concat(talentImportRuns()).slice(0, 12); _talSave(); }
/* Shares.
   Two are seeded in code so the partner view has a fixed, shareable URL that
   works in any browser - /share/talent/talentcorp-demo (live) and
   /share/talent/expired-demo (the 410 state). Links an admin creates are
   stored per browser on top of these. */
const TALENT_DEMO_SHARES = [
  { id: 's-demo-live', token: 'talentcorp-demo', partner_name: 'Talent Corp (sample)', demo: true,
    filter: Object.assign({}, { program: [], institution: [], location: [], certification: [], skills: [], completion_status: ['certified'], availability_status: ['available'], available_from: '' }),
    q: '', created_at: '2026-09-20T09:00:00+08:00', expires_at: '2027-12-31', revoked: false },
  { id: 's-demo-expired', token: 'expired-demo', partner_name: 'Expired example', demo: true,
    filter: { program: [], institution: [], location: [], certification: [], skills: [], completion_status: [], availability_status: [], available_from: '' },
    q: '', created_at: '2026-01-05T09:00:00+08:00', expires_at: '2026-02-05', revoked: false },
];
function talentShares() {
  const mine = _talState.shares || [];
  const revoked = _talState.revoked || {};
  const stamp = (s) => (revoked[s.id] ? Object.assign({}, s, { revoked: true }) : s);
  return mine.map(stamp).concat(TALENT_DEMO_SHARES.map(stamp));
}
function createTalentShare(share) { _talState.shares = [share].concat(talentShares()); _talSave(); return share; }
function revokeTalentShare(id) {
  _talState.revoked = Object.assign({}, _talState.revoked, { [id]: true }); _talSave();
}
function getTalentShare(token) { return talentShares().filter(s => s.token === token)[0] || null; }
function shareIsExpired(s) { return !!s && (s.revoked || new Date(s.expires_at).getTime() < Date.now()); }
/* View / open events (AC5) */
function recordShareEvent(token, kind, profileId) {
  const ev = _talState.events || {};
  const list = ev[token] || [];
  ev[token] = list.concat([{ kind, profileId: profileId || null, at: new Date().toISOString(), viewer: _talViewerId() }]);
  _talState.events = ev; _talSave();
}
function shareAnalytics(token) {
  const list = (_talState.events || {})[token] || [];
  const views = list.filter(e => e.kind === 'view');
  return {
    views: views.length,
    unique_viewers: new Set(views.map(e => e.viewer)).size,
    opened_profiles: new Set(list.filter(e => e.kind === 'open').map(e => e.profileId)).size,
    interests: list.filter(e => e.kind === 'interest').length,
    last_viewed_at: list.length ? list[list.length - 1].at : null,
  };
}
function _talViewerId() {
  if (!_talState.viewer) { _talState.viewer = 'v-' + Math.random().toString(36).slice(2, 8); }
  return _talState.viewer;
}

/* ---------- Filtering (mirrors GET /api/talent query params) ---------- */
const TALENT_EMPTY_FILTER = { program: [], institution: [], location: [], certification: [], skills: [], availability_status: [], completion_status: [], available_from: '' };
function talentFilter(profiles, f, opts) {
  const o = opts || {};
  const has = (arr) => arr && arr.length;
  return profiles.filter(p => {
    /* External callers only ever see consented profiles - the gate lives here
       so no share path can forget it (AC4). */
    if (o.externalOnly && !talentConsent(p)) return false;
    if (has(f.program) && f.program.indexOf(p.current_program) < 0) return false;
    if (has(f.institution) && f.institution.indexOf(p.institution) < 0) return false;
    if (has(f.location) && f.location.indexOf(p.location) < 0) return false;
    if (has(f.availability_status) && f.availability_status.indexOf(p.availability_status) < 0) return false;
    if (has(f.completion_status) && f.completion_status.indexOf(p.completion_status) < 0) return false;
    if (has(f.certification) && !p.certifications.some(c => f.certification.indexOf(c.name) >= 0)) return false;
    if (has(f.skills) && !f.skills.every(s => p.skills.indexOf(s) >= 0)) return false;
    if (f.available_from && p.availability_date > f.available_from) return false;
    if (o.q) {
      const hay = (p.full_name + ' ' + p.institution + ' ' + p.skills.join(' ') + ' ' + p.current_program).toLowerCase();
      if (hay.indexOf(o.q.toLowerCase()) < 0) return false;
    }
    return true;
  });
}
function talentFilterSummary(f) {
  const parts = [];
  Object.keys(f).forEach(k => {
    if (k === 'available_from') { if (f[k]) parts.push('available by ' + f[k]); return; }
    if (f[k] && f[k].length) parts.push(k.replace(/_/g, ' ') + ': ' + f[k].join(', '));
  });
  return parts.length ? parts.join(' · ') : 'All talent';
}
const TALENT_ALL_CERTS = (() => {
  const s = new Set(); TALENT_PROFILES.forEach(p => p.certifications.forEach(c => s.add(c.name))); return [...s].sort();
})();

/* ---------- QR (AC7) ----------
   qrcode-generator is loaded from the CDN in index.html; if it is missing we
   show the URL instead of a broken image rather than failing the page. */
function talentQrDataUrl(text, cell) {
  try {
    if (typeof qrcode !== 'function') return null;
    const qr = qrcode(0, 'M');
    qr.addData(text);
    qr.make();
    return qr.createDataURL(cell || 6, 8);
  } catch (e) { return null; }
}
function talentShareUrl(token) { return location.origin + '/share/talent/' + token; }

/* ---------- Shared bits ---------- */
const TAL_STATUS_TONE = { certified: 'bg-green-50 text-green-700', completed: 'bg-blue-50 text-blue-700', in_progress: 'bg-neutral-100 text-neutral-600' };
const TAL_AVAIL_TONE = { available: 'bg-green-50 text-green-700', placed: 'bg-blue-50 text-blue-700', unavailable: 'bg-neutral-100 text-neutral-500' };
function TalentPill({ children, className = '' }) {
  return <span className={`px-2.5 h-6 inline-flex items-center rounded-full text-[11px] font-semibold ${className}`}>{children}</span>;
}
function TalentTabs({ active }) {
  const tabs = [['Directory', '/app/hub/talent'], ['Import & sources', '/app/hub/talent/import'], ['Shares & analytics', '/app/hub/talent/shares']];
  return (
    <div className="flex items-center gap-1 border-b border-neutral-200 mb-5 overflow-x-auto">
      {tabs.map(([l, to]) => (
        <Link key={to} to={to} className={`px-4 py-2.5 text-sm font-semibold border-b-2 -mb-px whitespace-nowrap transition-colors ${active === to ? 'border-neutral-900 text-neutral-900' : 'border-transparent text-neutral-500 hover:text-neutral-800'}`}>{l}</Link>
      ))}
    </div>
  );
}
/* Multi-select used by every filter in the directory. */
function TalentMultiSelect({ label, options, value, onChange }) {
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const h = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    document.addEventListener('click', h); return () => document.removeEventListener('click', h);
  }, [open]);
  const toggle = (o) => onChange(value.indexOf(o) >= 0 ? value.filter(v => v !== o) : value.concat([o]));
  return (
    <div className="relative" ref={ref}>
      <button onClick={() => setOpen(o => !o)}
        className={`h-9 px-3 rounded-lg border text-sm inline-flex items-center gap-2 transition-colors ${value.length ? 'border-neutral-900 bg-neutral-900 text-white' : 'border-neutral-300 bg-white text-neutral-700 hover:border-neutral-400'}`}>
        {label}{value.length ? ` (${value.length})` : ''}
        <Icon name="chevronDown" size={14} className={open ? 'rotate-180 transition-transform' : 'transition-transform'} />
      </button>
      {open && (
        <div className="absolute z-40 mt-1.5 w-[240px] max-h-[280px] overflow-y-auto bg-white border border-neutral-200 rounded-xl py-1.5" style={{ boxShadow: '0 12px 32px rgba(0,0,0,0.14)' }}>
          {options.map(o => (
            <button key={o} onClick={() => toggle(o)} className="w-full flex items-center gap-2.5 px-3 py-1.5 text-left text-[13px] hover:bg-neutral-50">
              <span className={`w-4 h-4 rounded border flex items-center justify-center flex-shrink-0 ${value.indexOf(o) >= 0 ? 'bg-neutral-900 border-neutral-900' : 'border-neutral-300'}`}>
                {value.indexOf(o) >= 0 && <Icon name="check" size={11} className="text-white" strokeWidth={3} />}
              </span>
              <span className="truncate">{String(o).replace(/_/g, ' ')}</span>
            </button>
          ))}
          {value.length > 0 && <button onClick={() => onChange([])} className="w-full text-left px-3 py-1.5 text-[12px] font-semibold text-neutral-500 hover:text-neutral-900 border-t border-neutral-100 mt-1">Clear</button>}
        </div>
      )}
    </div>
  );
}
function TalentFilterBar({ f, setF, showConsent, consentOnly, setConsentOnly }) {
  const set = (k) => (v) => setF(Object.assign({}, f, { [k]: v }));
  return (
    <div className="flex items-center gap-2 flex-wrap">
      <TalentMultiSelect label="Programme" options={TALENT_PROGRAMS} value={f.program} onChange={set('program')} />
      <TalentMultiSelect label="Institution" options={TALENT_INSTITUTIONS} value={f.institution} onChange={set('institution')} />
      <TalentMultiSelect label="Location" options={TALENT_LOCATIONS} value={f.location} onChange={set('location')} />
      <TalentMultiSelect label="Certification" options={TALENT_ALL_CERTS} value={f.certification} onChange={set('certification')} />
      <TalentMultiSelect label="Skills" options={TALENT_SKILLS} value={f.skills} onChange={set('skills')} />
      <TalentMultiSelect label="Availability" options={TALENT_AVAILABILITY} value={f.availability_status} onChange={set('availability_status')} />
      <TalentMultiSelect label="Status" options={TALENT_STATUSES} value={f.completion_status} onChange={set('completion_status')} />
      <label className="h-9 px-3 rounded-lg border border-neutral-300 bg-white text-sm inline-flex items-center gap-2 text-neutral-600">
        Available by
        <input type="date" value={f.available_from} onChange={e => setF(Object.assign({}, f, { available_from: e.target.value }))} className="text-sm outline-none bg-transparent" />
      </label>
      {showConsent && (
        <button onClick={() => setConsentOnly(!consentOnly)}
          className={`h-9 px-3 rounded-lg border text-sm inline-flex items-center gap-2 transition-colors ${consentOnly ? 'border-green-600 bg-green-50 text-green-700' : 'border-neutral-300 bg-white text-neutral-700'}`}>
          <Icon name="shield" size={14} /> Consented only
        </button>
      )}
    </div>
  );
}

/* ---------- Admin: talent directory (EPIC C2) ---------- */
function TalentDirectoryPage() {
  useTalentState();
  const [f, setF] = React.useState(TALENT_EMPTY_FILTER);
  const [q, setQ] = React.useState('');
  const [consentOnly, setConsentOnly] = React.useState(false);
  const [share, setShare] = React.useState(false);
  const [saved, setSaved] = React.useState(() => (_talState.savedFilters || []));
  const rows = talentFilter(TALENT_PROFILES, f, { q, externalOnly: consentOnly });
  const consented = rows.filter(talentConsent).length;
  const saveCurrent = () => {
    const name = prompt('Name this filter (e.g. "Talent Corp — certified, available, Klang Valley")');
    if (!name) return;
    const next = [{ name, f, q }].concat(saved).slice(0, 8);
    setSaved(next); _talState.savedFilters = next; _talSave();
  };
  return (
    <UserShell route="/app/hub/talent">
      <div className="max-w-[1280px]">
        <div className="flex items-start justify-between gap-4 flex-wrap mb-4">
          <div>
            <h1 className="text-3xl font-bold text-ink" style={{ fontFamily: _pp }}>Talent directory</h1>
            <p className="text-neutral-500 mt-1">One profile per person, aggregated across every programme. Filter, then share a scoped, consented view with a partner.</p>
          </div>
          <div className="flex items-center gap-2">
            <Link to="/app/hub/talent/import"><button className="h-10 px-4 rounded-full border border-neutral-300 text-sm font-semibold inline-flex items-center gap-2 hover:bg-neutral-50"><Icon name="upload" size={15} /> Import</button></Link>
            <button onClick={() => setShare(true)} className="h-10 px-5 rounded-full bg-neutral-900 text-white text-sm font-semibold inline-flex items-center gap-2 hover:bg-black"><Icon name="send" size={15} /> Create share link</button>
          </div>
        </div>
        <TalentTabs active="/app/hub/talent" />

        <div className="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-5">
          {[['Profiles', TALENT_PROFILES.length], ['Matching filter', rows.length], ['Consented to share', TALENT_PROFILES.filter(talentConsent).length], ['Available now', TALENT_PROFILES.filter(p => p.availability_status === 'available').length]].map(([k, v]) => (
            <div key={k} className="bg-white border border-neutral-200 rounded-2xl px-5 py-4">
              <div className="text-2xl font-bold text-neutral-900 num-tabular">{v}</div>
              <div className="text-xs text-neutral-500 mt-0.5">{k}</div>
            </div>
          ))}
        </div>

        <div className="bg-white border border-neutral-200 rounded-2xl p-4 mb-4">
          <div className="flex items-center gap-2 mb-3">
            <div className="relative flex-1 min-w-[220px] max-w-[340px]">
              <Icon name="search" size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-neutral-400" />
              <input value={q} onChange={e => setQ(e.target.value)} placeholder="Search name, institution, skill…" className="w-full h-9 pl-10 pr-3 text-sm border border-neutral-300 rounded-lg focus:outline-none focus:border-neutral-500" />
            </div>
            <div className="flex-1" />
            <button onClick={saveCurrent} className="h-9 px-3 rounded-lg border border-neutral-300 text-sm font-semibold hover:bg-neutral-50 inline-flex items-center gap-1.5"><Icon name="save" size={14} /> Save filter</button>
            <button onClick={() => { setF(TALENT_EMPTY_FILTER); setQ(''); setConsentOnly(false); }} className="h-9 px-3 rounded-lg text-sm font-semibold text-neutral-500 hover:text-neutral-900">Reset</button>
          </div>
          <TalentFilterBar f={f} setF={setF} showConsent consentOnly={consentOnly} setConsentOnly={setConsentOnly} />
          {saved.length > 0 && (
            <div className="mt-3 pt-3 border-t border-neutral-100 flex items-center gap-2 flex-wrap">
              <span className="text-[12px] font-bold uppercase tracking-wide text-neutral-400">Saved</span>
              {saved.map((s, i) => (
                <button key={i} onClick={() => { setF(s.f); setQ(s.q || ''); }} className="h-7 px-3 rounded-full bg-neutral-100 text-[12px] font-semibold text-neutral-700 hover:bg-neutral-200">{s.name}</button>
              ))}
            </div>
          )}
        </div>

        <div className="bg-white border border-neutral-200 rounded-2xl overflow-x-auto">
          <table className="w-full text-sm min-w-[1040px]">
            <thead>
              <tr className="border-b border-neutral-100 text-neutral-500 text-left">
                {['Name', 'Programme', 'Institution', 'Location', 'Latest certification', 'Skills', 'Availability', 'Consent'].map(h => <th key={h} className="px-4 py-3 font-medium whitespace-nowrap">{h}</th>)}
              </tr>
            </thead>
            <tbody>
              {rows.length === 0 ? (
                <tr><td colSpan={8} className="px-4 py-14 text-center text-neutral-500">No profiles match these filters.</td></tr>
              ) : rows.map(p => (
                <tr key={p.id} className="border-b border-neutral-100 last:border-0 hover:bg-neutral-50">
                  <td className="px-4 py-3">
                    <Link to={`/app/hub/talent/${p.id}`} className="flex items-center gap-2.5 group">
                      <Avatar name={p.full_name} size={32} />
                      <span>
                        <span className="block font-semibold text-neutral-900 group-hover:text-blue-600 whitespace-nowrap">{p.full_name}</span>
                        {p.source_records.length > 1 && <span className="block text-[11px] text-neutral-400">merged from {p.source_records.length} sources</span>}
                      </span>
                    </Link>
                  </td>
                  <td className="px-4 py-3"><TalentPill className="bg-neutral-100 text-neutral-700">{p.current_program}</TalentPill></td>
                  <td className="px-4 py-3 text-neutral-600 whitespace-nowrap">{p.institution}</td>
                  <td className="px-4 py-3 text-neutral-600">{p.location}</td>
                  <td className="px-4 py-3 text-neutral-700">{p.latest_certification || <span className="text-neutral-300">—</span>}</td>
                  <td className="px-4 py-3">
                    <span className="flex flex-wrap gap-1">{p.skills.slice(0, 2).map(s => <TalentPill key={s} className="bg-blue-50 text-blue-700">{s}</TalentPill>)}{p.skills.length > 2 && <TalentPill className="bg-neutral-100 text-neutral-500">+{p.skills.length - 2}</TalentPill>}</span>
                  </td>
                  <td className="px-4 py-3"><TalentPill className={TAL_AVAIL_TONE[p.availability_status]}>{p.availability_status}</TalentPill></td>
                  <td className="px-4 py-3">
                    {talentConsent(p)
                      ? <span className="inline-flex items-center gap-1 text-[12px] font-semibold text-green-700"><Icon name="check" size={13} /> Shareable</span>
                      : <span className="inline-flex items-center gap-1 text-[12px] font-semibold text-neutral-400"><Icon name="lock" size={13} /> Not shared</span>}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
        <p className="text-[12px] text-neutral-500 mt-3">
          {rows.length} matching · {consented} consented. A share link built from this filter will show {consented} profile{consented === 1 ? '' : 's'} — profiles without consent are never included.
        </p>
      </div>
      <TalentShareModal open={share} onClose={() => setShare(false)} filter={f} q={q} />
    </UserShell>
  );
}

/* ---------- Admin: single profile (merge audit trail, AC2 + AC8) ---------- */
function TalentProfilePage({ id }) {
  useTalentState();
  const p = TALENT_PROFILES.filter(x => x.id === id)[0] || TALENT_PROFILES[0];
  const row = (k, v) => (
    <div key={k} className="flex items-start gap-3 py-2 border-b border-neutral-100 last:border-0">
      <span className="w-[190px] flex-shrink-0 text-[13px] text-neutral-500">{k}</span>
      <span className="text-[14px] text-neutral-900 min-w-0 break-words">{v}</span>
    </div>
  );
  return (
    <UserShell route="/app/hub/talent">
      <div className="max-w-[1000px]">
        <Link to="/app/hub/talent" className="inline-flex items-center gap-1 text-sm text-neutral-500 hover:text-neutral-900 mb-4"><Icon name="chevronLeft" size={14} /> Back to directory</Link>
        <div className="bg-white border border-neutral-200 rounded-2xl p-6">
          <div className="flex items-start gap-4 flex-wrap">
            <Avatar name={p.full_name} size={72} />
            <div className="flex-1 min-w-[220px]">
              <h1 className="text-2xl font-bold text-neutral-900">{p.full_name}</h1>
              <p className="text-neutral-500 mt-0.5">{p.institution} · {p.location}</p>
              <div className="flex items-center gap-2 mt-3 flex-wrap">
                <TalentPill className={TAL_STATUS_TONE[p.completion_status]}>{p.completion_status.replace('_', ' ')}</TalentPill>
                <TalentPill className={TAL_AVAIL_TONE[p.availability_status]}>{p.availability_status}</TalentPill>
                <TalentPill className="bg-neutral-100 text-neutral-700">{p.current_program} · {p.program_cohort}</TalentPill>
              </div>
            </div>
            <div className={`px-4 py-3 rounded-xl border ${talentConsent(p) ? 'border-green-200 bg-green-50' : 'border-neutral-200 bg-neutral-50'}`}>
              <p className="text-[11px] font-bold uppercase tracking-wide text-neutral-500">Employer sharing</p>
              <p className={`text-sm font-bold mt-0.5 ${talentConsent(p) ? 'text-green-700' : 'text-neutral-500'}`}>{talentConsent(p) ? 'Consented' : 'No consent'}</p>
              {talentConsent(p) && talentConsentTs(p) && <p className="text-[11px] text-neutral-500 mt-0.5">since {String(talentConsentTs(p)).slice(0, 10)}</p>}
            </div>
          </div>
        </div>

        <div className="grid lg:grid-cols-2 gap-5 mt-5">
          <div className="bg-white border border-neutral-200 rounded-2xl p-5">
            <h2 className="font-bold text-neutral-900 mb-2">Canonical record</h2>
            {row('Profile id', <span className="font-mono text-[12px]">{p.id}</span>)}
            {row('Email (dedup key 1)', p.email)}
            {row('Phone (dedup key 2)', p.phone)}
            {row('National ID (dedup key 3)', <span><span className="font-mono text-[12px]">{p.national_id_hash}</span><span className="block text-[11px] text-neutral-400 mt-0.5">Hash only — the raw IC/passport is never stored.</span></span>)}
            {row('Availability date', p.availability_date)}
            {row('Skills', <span className="flex flex-wrap gap-1">{p.skills.map(s => <TalentPill key={s} className="bg-blue-50 text-blue-700">{s}</TalentPill>)}</span>)}
            {row('Created / updated', p.created_at + ' / ' + p.updated_at)}
          </div>
          <div className="space-y-5">
            <div className="bg-white border border-neutral-200 rounded-2xl p-5">
              <h2 className="font-bold text-neutral-900 mb-3">Certifications</h2>
              {p.certifications.length === 0 ? <p className="text-sm text-neutral-500">No certification recorded yet.</p> : (
                <div className="space-y-2">
                  {p.certifications.map((c, i) => (
                    <div key={i} className="flex items-center gap-3 p-3 rounded-xl bg-neutral-50">
                      <span className="w-9 h-9 rounded-lg bg-white border border-neutral-200 flex items-center justify-center flex-shrink-0"><Icon name="award" size={17} className="text-neutral-700" /></span>
                      <span className="min-w-0 flex-1"><span className="block font-semibold text-[14px] text-neutral-900 truncate">{c.name}</span><span className="block text-[12px] text-neutral-500">Issued {c.issued_at}</span></span>
                    </div>
                  ))}
                </div>
              )}
            </div>
            <div className="bg-white border border-neutral-200 rounded-2xl p-5">
              <h2 className="font-bold text-neutral-900 mb-1">Source records</h2>
              <p className="text-[12px] text-neutral-500 mb-3">Every row this profile was built from, and what it was matched on. One person, one profile.</p>
              <div className="space-y-2">
                {p.source_records.map((s, i) => (
                  <div key={i} className="flex items-center gap-3 text-[13px] p-2.5 rounded-lg border border-neutral-100">
                    <TalentPill className="bg-neutral-900 text-white">{s.source_system}</TalentPill>
                    <span className="font-mono text-[12px] text-neutral-500">{s.source_record_ref}</span>
                    <span className="flex-1" />
                    <span className="text-neutral-500">{s.matched_on === 'seed' ? 'first import' : 'merged on ' + s.matched_on}</span>
                  </div>
                ))}
              </div>
            </div>
          </div>
        </div>
      </div>
    </UserShell>
  );
}

/* ---------- Create share link (EPIC E1/E2) ---------- */
function TalentShareModal({ open, onClose, filter, q }) {
  const [partner, setPartner] = React.useState('Talent Corp');
  const [expires, setExpires] = React.useState(() => {
    const d = new Date(); d.setDate(d.getDate() + 30); return d.toISOString().slice(0, 10);
  });
  const [made, setMade] = React.useState(null);
  const [copied, setCopied] = React.useState(false);
  React.useEffect(() => { if (open) { setMade(null); setCopied(false); } }, [open]);
  if (!open) return null;
  const f = filter || TALENT_EMPTY_FILTER;
  const included = talentFilter(TALENT_PROFILES, f, { q, externalOnly: true });
  const matching = talentFilter(TALENT_PROFILES, f, { q });
  const hidden = matching.length - included.length;
  const create = () => {
    const token = 'tc-' + Math.random().toString(36).slice(2, 8) + Math.random().toString(36).slice(2, 6);
    const share = { id: 's-' + Date.now(), token, partner_name: partner || 'Partner', filter: f, q: q || '', expires_at: expires, created_at: new Date().toISOString(), revoked: false };
    createTalentShare(share); setMade(share);
  };
  const url = made ? talentShareUrl(made.token) : '';
  const qr = made ? talentQrDataUrl(url, 5) : null;
  return (
    <div className="fixed inset-0 z-[130] flex items-center justify-center px-4 bg-black/50 backdrop-blur-sm" onClick={onClose}>
      <div className="w-full max-w-lg bg-white rounded-2xl shadow-2xl p-6 max-h-[88vh] overflow-y-auto" onClick={e => e.stopPropagation()}>
        {!made ? (<>
          <div className="flex items-start justify-between">
            <div><h3 className="text-lg font-bold text-ink" style={{ fontFamily: _pp }}>Create share link</h3><p className="text-[13px] text-neutral-500 mt-0.5">A read-only, tokenised view of this filter.</p></div>
            <button onClick={onClose} className="text-neutral-400 hover:text-neutral-700"><Icon name="close" size={20} /></button>
          </div>
          <label className="block mt-4 text-[13px] font-semibold text-neutral-700">Partner name
            <input value={partner} onChange={e => setPartner(e.target.value)} className="mt-1 w-full h-10 px-3 border border-neutral-300 rounded-lg text-sm focus:outline-none focus:border-neutral-500" placeholder="Talent Corp" />
          </label>
          <label className="block mt-3 text-[13px] font-semibold text-neutral-700">Expires on
            <input type="date" value={expires} onChange={e => setExpires(e.target.value)} className="mt-1 w-full h-10 px-3 border border-neutral-300 rounded-lg text-sm focus:outline-none focus:border-neutral-500" />
          </label>
          <div className="mt-4 p-3 rounded-xl bg-neutral-50 border border-neutral-200">
            <p className="text-[12px] font-bold uppercase tracking-wide text-neutral-400">Filter</p>
            <p className="text-[13px] text-neutral-800 mt-1">{talentFilterSummary(f)}{q ? ` · search "${q}"` : ''}</p>
          </div>
          <div className="mt-3 flex items-start gap-2.5 p-3 rounded-xl border border-green-200 bg-green-50">
            <Icon name="shield" size={16} className="text-green-700 mt-0.5 flex-shrink-0" />
            <p className="text-[13px] text-green-900">
              <span className="font-semibold">{included.length} profile{included.length === 1 ? '' : 's'} will be visible.</span>
              {hidden > 0 && <span> {hidden} match the filter but have not consented to employer sharing, and are excluded.</span>}
            </p>
          </div>
          <div className="mt-5 flex justify-end gap-2">
            <button onClick={onClose} className="h-10 px-4 rounded-full border border-neutral-200 text-sm font-semibold hover:bg-neutral-50">Cancel</button>
            <button onClick={create} className="h-10 px-5 rounded-full bg-neutral-900 text-white text-sm font-semibold hover:bg-black">Create link</button>
          </div>
        </>) : (<>
          <div className="flex items-start justify-between">
            <div><h3 className="text-lg font-bold text-ink" style={{ fontFamily: _pp }}>Share link ready</h3><p className="text-[13px] text-neutral-500 mt-0.5">For {made.partner_name} · expires {made.expires_at}</p></div>
            <button onClick={onClose} className="text-neutral-400 hover:text-neutral-700"><Icon name="close" size={20} /></button>
          </div>
          <div className="mt-4 flex items-center gap-2">
            <input readOnly value={url} className="flex-1 h-10 px-3 border border-neutral-300 rounded-lg text-[13px] font-mono bg-neutral-50" />
            <button onClick={() => { try { navigator.clipboard.writeText(url); setCopied(true); } catch (e) {} }} className="h-10 px-4 rounded-lg bg-neutral-900 text-white text-sm font-semibold inline-flex items-center gap-1.5"><Icon name="copy" size={14} /> {copied ? 'Copied' : 'Copy'}</button>
          </div>
          <div className="mt-4 flex items-center gap-4 p-4 rounded-xl border border-neutral-200">
            {qr ? <img src={qr} alt="QR code for the share link" className="w-[132px] h-[132px] flex-shrink-0" /> : <div className="w-[132px] h-[132px] rounded bg-neutral-100 flex items-center justify-center text-[11px] text-neutral-400 text-center px-2">QR library not loaded</div>}
            <div className="min-w-0">
              <p className="font-semibold text-neutral-900 text-[14px]">Scan to open the same link</p>
              <p className="text-[12px] text-neutral-500 mt-1">Print it on a one-pager for the Talent Corp session, or drop it in a deck.</p>
              {qr && <a href={qr} download={`mereka-talent-${made.token}.gif`} className="mt-2 inline-flex items-center gap-1.5 text-[13px] font-semibold text-blue-600 hover:underline"><Icon name="download" size={14} /> Download QR</a>}
            </div>
          </div>
          <div className="mt-5 flex justify-end gap-2">
            <Link to="/app/hub/talent/shares"><button className="h-10 px-4 rounded-full border border-neutral-200 text-sm font-semibold hover:bg-neutral-50">View all shares</button></Link>
            <Link to={`/share/talent/${made.token}`}><button className="h-10 px-5 rounded-full bg-neutral-900 text-white text-sm font-semibold hover:bg-black">Open partner view</button></Link>
          </div>
        </>)}
      </div>
    </div>
  );
}

/* ---------- Admin: shares + analytics (EPIC F2) ---------- */
function TalentSharesPage() {
  useTalentState();
  const shares = talentShares();
  const [openQr, setOpenQr] = React.useState(null);
  return (
    <UserShell route="/app/hub/talent">
      <div className="max-w-[1180px]">
        <div className="flex items-start justify-between gap-4 flex-wrap mb-4">
          <div><h1 className="text-3xl font-bold text-ink" style={{ fontFamily: _pp }}>Shares &amp; analytics</h1><p className="text-neutral-500 mt-1">Every partner link, what it exposes, and how it is being used.</p></div>
          <Link to="/app/hub/talent"><button className="h-10 px-4 rounded-full border border-neutral-300 text-sm font-semibold hover:bg-neutral-50">Back to directory</button></Link>
        </div>
        <TalentTabs active="/app/hub/talent/shares" />
        {shares.length === 0 ? (
          <div className="bg-white border border-neutral-200 rounded-2xl py-16 text-center">
            <span className="w-14 h-14 rounded-full bg-neutral-100 grid place-items-center mx-auto mb-4"><Icon name="send" size={22} className="text-neutral-400" /></span>
            <h3 className="font-bold text-neutral-900">No share links yet</h3>
            <p className="text-sm text-neutral-500 mt-1">Filter the directory, then use “Create share link”.</p>
            <Link to="/app/hub/talent"><button className="mt-4 h-10 px-5 rounded-full bg-neutral-900 text-white text-sm font-semibold">Open directory</button></Link>
          </div>
        ) : (
          <div className="space-y-4">
            {shares.map(s => {
              const a = shareAnalytics(s.token);
              const visible = talentFilter(TALENT_PROFILES, s.filter, { q: s.q, externalOnly: true });
              const expired = shareIsExpired(s);
              return (
                <div key={s.id} className="bg-white border border-neutral-200 rounded-2xl p-5">
                  <div className="flex items-start justify-between gap-4 flex-wrap">
                    <div className="min-w-0">
                      <div className="flex items-center gap-2 flex-wrap">
                        <h3 className="font-bold text-neutral-900">{s.partner_name}</h3>
                        {expired ? <TalentPill className="bg-red-50 text-red-600">{s.revoked ? 'revoked' : 'expired'}</TalentPill> : <TalentPill className="bg-green-50 text-green-700">active</TalentPill>}
                        {s.demo && <TalentPill className="bg-amber-50 text-amber-700">sample link</TalentPill>}
                        <TalentPill className="bg-neutral-100 text-neutral-600">{visible.length} profiles</TalentPill>
                      </div>
                      <p className="text-[13px] text-neutral-500 mt-1">{talentFilterSummary(s.filter)}{s.q ? ` · search "${s.q}"` : ''}</p>
                      <p className="text-[12px] text-neutral-400 mt-1">Created {String(s.created_at).slice(0, 10)} · expires {s.expires_at}</p>
                      <div className="mt-2 flex items-center gap-2">
                        <code className="text-[12px] bg-neutral-50 border border-neutral-200 rounded px-2 py-1 font-mono">/share/talent/{s.token}</code>
                        <button onClick={() => { try { navigator.clipboard.writeText(talentShareUrl(s.token)); } catch (e) {} }} className="text-[12px] font-semibold text-blue-600 hover:underline">Copy</button>
                        <button onClick={() => setOpenQr(openQr === s.id ? null : s.id)} className="text-[12px] font-semibold text-blue-600 hover:underline">QR</button>
                        <Link to={`/share/talent/${s.token}`} className="text-[12px] font-semibold text-blue-600 hover:underline">Open</Link>
                        {!s.revoked && !s.demo && <button onClick={() => revokeTalentShare(s.id)} className="text-[12px] font-semibold text-red-600 hover:underline">Revoke</button>}
                      </div>
                      {openQr === s.id && (() => { const d = talentQrDataUrl(talentShareUrl(s.token), 4); return d ? <img src={d} alt="" className="mt-3 w-[116px] h-[116px]" /> : <p className="mt-3 text-[12px] text-neutral-400">QR library not loaded.</p>; })()}
                    </div>
                    <div className="grid grid-cols-4 gap-3 text-center">
                      {[['Views', a.views], ['Unique', a.unique_viewers], ['Profiles opened', a.opened_profiles], ['Interest', a.interests]].map(([k, v]) => (
                        <div key={k} className="px-4 py-2 rounded-xl bg-neutral-50 min-w-[84px]">
                          <div className="text-xl font-bold num-tabular text-neutral-900">{v}</div>
                          <div className="text-[11px] text-neutral-500">{k}</div>
                        </div>
                      ))}
                    </div>
                  </div>
                  {a.last_viewed_at && <p className="text-[12px] text-neutral-400 mt-3 pt-3 border-t border-neutral-100">Last activity {new Date(a.last_viewed_at).toLocaleString()}</p>}
                </div>
              );
            })}
          </div>
        )}
      </div>
    </UserShell>
  );
}

/* ---------- Admin: import & sources (EPIC B) ---------- */
function TalentImportPage() {
  useTalentState();
  const [src, setSrc] = React.useState(TALENT_SOURCES[2].id);
  const [report, setReport] = React.useState(null);
  const runs = talentImportRuns();
  const source = TALENT_SOURCES.filter(s => s.id === src)[0];
  const runImport = () => {
    /* Idempotent by design (AC1): the first run of a source creates, every
       later run only updates. Merges are the rows matched to a person who
       already arrived from another programme. */
    const before = runs.filter(r => r.source === src).length;
    const inSource = TALENT_PROFILES.filter(p => p.source_records.some(r => r.source_system === src));
    const merged = TALENT_PROFILES.filter(p => p.source_records.length > 1 && p.source_records.some(r => r.source_system === src && r.matched_on !== 'seed')).length;
    const rep = {
      source: src, at: new Date().toISOString(), rows: source.rows,
      created: before === 0 ? inSource.length : 0,
      updated: before === 0 ? 0 : inSource.length,
      merged, errors: before === 0 ? [{ row: 17, reason: 'Missing email and phone — cannot dedupe, skipped' }] : [],
      runNo: before + 1,
    };
    recordTalentImport(rep); setReport(rep);
  };
  return (
    <UserShell route="/app/hub/talent">
      <div className="max-w-[1180px]">
        <div className="mb-4">
          <h1 className="text-3xl font-bold text-ink" style={{ fontFamily: _pp }}>Import &amp; sources</h1>
          <p className="text-neutral-500 mt-1">Every programme keeps its own sheet. Each import maps those columns onto the canonical profile and reconciles against what is already here.</p>
        </div>
        <TalentTabs active="/app/hub/talent/import" />
        <div className="grid lg:grid-cols-[320px_1fr] gap-5">
          <div className="bg-white border border-neutral-200 rounded-2xl p-3">
            <p className="text-[12px] font-bold uppercase tracking-wide text-neutral-400 px-2 py-1">Sources</p>
            {TALENT_SOURCES.map(s => (
              <button key={s.id} onClick={() => { setSrc(s.id); setReport(null); }}
                className={`w-full text-left px-3 py-2.5 rounded-xl transition-colors ${src === s.id ? 'bg-neutral-900 text-white' : 'hover:bg-neutral-50'}`}>
                <span className="block text-[14px] font-semibold">{s.label}</span>
                <span className={`block text-[12px] ${src === s.id ? 'text-white/70' : 'text-neutral-500'}`}>{s.kind} · {s.rows} rows · {s.owner}</span>
                {s.unconfirmed && <span className={`inline-block mt-1 text-[11px] font-semibold ${src === s.id ? 'text-amber-300' : 'text-amber-600'}`}>format unconfirmed</span>}
              </button>
            ))}
            <div className="border-t border-neutral-100 mt-2 pt-2">
              <button className="w-full text-left px-3 py-2.5 rounded-xl hover:bg-neutral-50">
                <span className="block text-[14px] font-semibold inline-flex items-center gap-1.5"><Icon name="upload" size={14} /> Generic CSV</span>
                <span className="block text-[12px] text-neutral-500">Fallback for any new programme</span>
              </button>
            </div>
          </div>
          <div className="space-y-5">
            <div className="bg-white border border-neutral-200 rounded-2xl p-5">
              <div className="flex items-start justify-between gap-3 flex-wrap">
                <div><h2 className="font-bold text-neutral-900">{source.label}</h2><p className="text-[13px] text-neutral-500 mt-0.5">Column mapping onto the canonical profile</p></div>
                <button onClick={runImport} className="h-10 px-5 rounded-full bg-neutral-900 text-white text-sm font-semibold inline-flex items-center gap-2 hover:bg-black"><Icon name="refresh" size={15} /> Run import</button>
              </div>
              <div className="mt-4 overflow-x-auto">
                <table className="w-full text-sm min-w-[520px]">
                  <thead><tr className="text-neutral-500 text-left border-b border-neutral-100"><th className="py-2 font-medium">Source column</th><th className="py-2 font-medium w-10"></th><th className="py-2 font-medium">Canonical field</th></tr></thead>
                  <tbody>
                    {source.map.map(([from, to], i) => (
                      <tr key={i} className="border-b border-neutral-50 last:border-0">
                        <td className="py-2 text-neutral-700">{from}</td>
                        <td className="py-2 text-neutral-300"><Icon name="arrowRight" size={14} /></td>
                        <td className="py-2"><code className="text-[12px] font-mono bg-neutral-50 border border-neutral-200 rounded px-1.5 py-0.5">{to}</code></td>
                      </tr>
                    ))}
                  </tbody>
                </table>
              </div>
            </div>

            {report && (
              <div className="bg-white border border-neutral-200 rounded-2xl p-5">
                <h2 className="font-bold text-neutral-900">Reconciliation report · run #{report.runNo}</h2>
                <p className="text-[13px] text-neutral-500 mt-0.5">{report.rows} rows read from {report.source} at {new Date(report.at).toLocaleTimeString()}</p>
                <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 mt-4">
                  {[['Created', report.created, 'text-green-700'], ['Updated', report.updated, 'text-blue-700'], ['Merged', report.merged, 'text-purple-700'], ['Errors', report.errors.length, 'text-red-600']].map(([k, v, c]) => (
                    <div key={k} className="px-4 py-3 rounded-xl bg-neutral-50 text-center"><div className={`text-2xl font-bold num-tabular ${c}`}>{v}</div><div className="text-[11px] text-neutral-500 mt-0.5">{k}</div></div>
                  ))}
                </div>
                {report.runNo > 1 && (
                  <p className="mt-3 text-[13px] text-green-800 bg-green-50 border border-green-200 rounded-xl px-3 py-2">
                    Re-running the same source created <strong>0</strong> new profiles — existing people were matched and updated instead.
                  </p>
                )}
                {report.errors.length > 0 && (
                  <div className="mt-3">
                    <p className="text-[12px] font-bold uppercase tracking-wide text-neutral-400 mb-1">Errors</p>
                    {report.errors.map((e, i) => <p key={i} className="text-[13px] text-red-700">Row {e.row}: {e.reason}</p>)}
                  </div>
                )}
              </div>
            )}

            {runs.length > 0 && (
              <div className="bg-white border border-neutral-200 rounded-2xl p-5">
                <h2 className="font-bold text-neutral-900 mb-3">Run history</h2>
                <div className="space-y-1.5">
                  {runs.map((r, i) => (
                    <div key={i} className="flex items-center gap-3 text-[13px] py-1.5 border-b border-neutral-50 last:border-0">
                      <TalentPill className="bg-neutral-100 text-neutral-700">{r.source}</TalentPill>
                      <span className="text-neutral-500">{new Date(r.at).toLocaleString()}</span>
                      <span className="flex-1" />
                      <span className="text-neutral-700">+{r.created} created · {r.updated} updated · {r.merged} merged</span>
                    </div>
                  ))}
                </div>
              </div>
            )}
          </div>
        </div>
      </div>
    </UserShell>
  );
}

/* ---------- Partner view (public, tokenised) ----------
   Read-only. Shows consented profiles only, never contact details, and
   records a view on open and an open per profile card (AC4, AC5, AC6). */
function TalentPartnerPage({ token }) {
  useTalentState();
  const share = getTalentShare(token);
  const [opened, setOpened] = React.useState(null);
  const [interest, setInterest] = React.useState(null);
  const [sent, setSent] = React.useState({});
  React.useEffect(() => { if (share && !shareIsExpired(share)) recordShareEvent(token, 'view'); }, [token]);

  if (!share) {
    return (
      <PublicShell route="/share">
        <div className="max-w-[560px] mx-auto px-5 py-24 text-center">
          <span className="w-14 h-14 rounded-full bg-neutral-100 grid place-items-center mx-auto mb-4"><Icon name="lock" size={22} className="text-neutral-400" /></span>
          <h1 className="text-2xl font-bold text-ink" style={{ fontFamily: _pp }}>Link not found</h1>
          <p className="text-neutral-500 mt-2">This share link does not exist. Ask your Mereka contact for a current one.</p>
        </div>
      </PublicShell>
    );
  }
  if (shareIsExpired(share)) {
    return (
      <PublicShell route="/share">
        <div className="max-w-[560px] mx-auto px-5 py-24 text-center">
          <span className="w-14 h-14 rounded-full bg-red-50 grid place-items-center mx-auto mb-4"><Icon name="clock" size={22} className="text-red-500" /></span>
          <h1 className="text-2xl font-bold text-ink" style={{ fontFamily: _pp }}>This link has {share.revoked ? 'been revoked' : 'expired'}</h1>
          <p className="text-neutral-500 mt-2">Shared with {share.partner_name} · {share.revoked ? 'revoked by Mereka' : 'expired on ' + share.expires_at}. The profiles behind it are no longer reachable through this URL.</p>
          <p className="text-[12px] text-neutral-400 mt-4 font-mono">HTTP 410 Gone</p>
        </div>
      </PublicShell>
    );
  }
  const list = talentFilter(TALENT_PROFILES, share.filter, { q: share.q, externalOnly: true });
  const open = (p) => { setOpened(p); recordShareEvent(token, 'open', p.id); };
  const express = (p) => { setInterest(p); };
  const confirmInterest = () => {
    recordShareEvent(token, 'interest', interest.id);
    setSent(Object.assign({}, sent, { [interest.id]: true }));
    setInterest(null);
  };
  return (
    <PublicShell route="/share">
      <div className="bg-neutral-50 min-h-screen">
        <div className="max-w-[1100px] mx-auto px-5 py-8">
          <div className="bg-white border border-neutral-200 rounded-2xl p-6 mb-5">
            <div className="flex items-start justify-between gap-4 flex-wrap">
              <div>
                <TalentPill className="bg-neutral-900 text-white">Shared with {share.partner_name}</TalentPill>
                <h1 className="text-3xl font-bold text-ink mt-3" style={{ fontFamily: _pp }}>Mereka talent</h1>
                <p className="text-neutral-500 mt-1">{talentFilterSummary(share.filter)}</p>
              </div>
              <div className="text-right">
                <p className="text-3xl font-bold num-tabular text-neutral-900">{list.length}</p>
                <p className="text-[12px] text-neutral-500">profiles</p>
              </div>
            </div>
            <div className="mt-4 flex items-start gap-2.5 text-[13px] text-neutral-600 border-t border-neutral-100 pt-4">
              <Icon name="shield" size={16} className="text-green-600 mt-0.5 flex-shrink-0" />
              <p>Every person here has consented to being shared with employers. Contact details stay with Mereka — use <strong>Express interest</strong> and we will make the introduction. This link expires on {share.expires_at}.</p>
            </div>
          </div>

          {list.length === 0 ? (
            <div className="bg-white border border-neutral-200 rounded-2xl py-16 text-center text-neutral-500">No profiles are currently shared through this link.</div>
          ) : (
            <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
              {list.map(p => (
                <div key={p.id} className="bg-white border border-neutral-200 rounded-2xl p-5 flex flex-col">
                  <div className="flex items-center gap-3">
                    <Avatar name={p.full_name} size={48} />
                    <div className="min-w-0">
                      <h3 className="font-bold text-neutral-900 truncate">{p.full_name}</h3>
                      <p className="text-[12px] text-neutral-500 truncate">{p.institution}</p>
                    </div>
                  </div>
                  <div className="mt-3 flex items-center gap-1.5 flex-wrap">
                    <TalentPill className="bg-neutral-100 text-neutral-700">{p.location}</TalentPill>
                    <TalentPill className={TAL_AVAIL_TONE[p.availability_status]}>{p.availability_status}</TalentPill>
                  </div>
                  <p className="text-[13px] text-neutral-600 mt-3">{p.latest_certification || 'In training'} · {p.current_program}</p>
                  <div className="mt-2 flex flex-wrap gap-1">{p.skills.slice(0, 3).map(s => <TalentPill key={s} className="bg-blue-50 text-blue-700">{s}</TalentPill>)}</div>
                  <div className="flex-1" />
                  <div className="mt-4 flex items-center gap-2">
                    <button onClick={() => open(p)} className="flex-1 h-9 rounded-lg border border-neutral-300 text-[13px] font-semibold hover:bg-neutral-50">View profile</button>
                    <button onClick={() => express(p)} disabled={!!sent[p.id]}
                      className={`flex-1 h-9 rounded-lg text-[13px] font-semibold ${sent[p.id] ? 'bg-green-50 text-green-700 border border-green-200' : 'bg-neutral-900 text-white hover:bg-black'}`}>
                      {sent[p.id] ? 'Interest sent' : 'Express interest'}
                    </button>
                  </div>
                </div>
              ))}
            </div>
          )}
          <p className="text-[12px] text-neutral-400 mt-6 text-center">Mereka · shared talent view · token {token}</p>
        </div>
      </div>

      {opened && (
        <div className="fixed inset-0 z-[130] flex items-center justify-center px-4 bg-black/50 backdrop-blur-sm" onClick={() => setOpened(null)}>
          <div className="w-full max-w-md bg-white rounded-2xl shadow-2xl p-6 max-h-[88vh] overflow-y-auto" onClick={e => e.stopPropagation()}>
            <div className="flex items-start justify-between">
              <div className="flex items-center gap-3"><Avatar name={opened.full_name} size={52} /><div><h3 className="text-lg font-bold text-ink" style={{ fontFamily: _pp }}>{opened.full_name}</h3><p className="text-[13px] text-neutral-500">{opened.institution} · {opened.location}</p></div></div>
              <button onClick={() => setOpened(null)} className="text-neutral-400 hover:text-neutral-700"><Icon name="close" size={20} /></button>
            </div>
            <div className="mt-4 space-y-3 text-[14px]">
              <div><p className="text-[12px] font-bold uppercase tracking-wide text-neutral-400">Programme</p><p className="text-neutral-900">{opened.current_program} · {opened.completion_status.replace('_', ' ')}</p></div>
              <div><p className="text-[12px] font-bold uppercase tracking-wide text-neutral-400">Certifications</p>
                {opened.certifications.length ? opened.certifications.map((c, i) => <p key={i} className="text-neutral-900">{c.name} <span className="text-neutral-400 text-[12px]">· {c.issued_at}</span></p>) : <p className="text-neutral-500">In training</p>}
              </div>
              <div><p className="text-[12px] font-bold uppercase tracking-wide text-neutral-400">Skills</p><span className="flex flex-wrap gap-1 mt-1">{opened.skills.map(s => <TalentPill key={s} className="bg-blue-50 text-blue-700">{s}</TalentPill>)}</span></div>
              <div><p className="text-[12px] font-bold uppercase tracking-wide text-neutral-400">Availability</p><p className="text-neutral-900">{opened.availability_status} from {opened.availability_date}</p></div>
              <p className="text-[12px] text-neutral-400 border-t border-neutral-100 pt-3">Contact details are held by Mereka and released after an introduction.</p>
            </div>
            <div className="mt-5 flex justify-end gap-2">
              <button onClick={() => setOpened(null)} className="h-10 px-4 rounded-full border border-neutral-200 text-sm font-semibold hover:bg-neutral-50">Close</button>
              <button onClick={() => { const p = opened; setOpened(null); express(p); }} className="h-10 px-5 rounded-full bg-neutral-900 text-white text-sm font-semibold hover:bg-black">Express interest</button>
            </div>
          </div>
        </div>
      )}

      {interest && (
        <div className="fixed inset-0 z-[140] flex items-center justify-center px-4 bg-black/50 backdrop-blur-sm" onClick={() => setInterest(null)}>
          <div className="w-full max-w-md bg-white rounded-2xl shadow-2xl p-6" onClick={e => e.stopPropagation()}>
            <h3 className="text-lg font-bold text-ink" style={{ fontFamily: _pp }}>Express interest</h3>
            <p className="text-[14px] text-neutral-600 mt-2">Mereka will be notified that {share.partner_name} is interested in <strong>{interest.full_name}</strong> and will follow up to arrange the introduction.</p>
            <label className="block mt-4 text-[13px] font-semibold text-neutral-700">Role or note (optional)
              <textarea rows={3} className="mt-1 w-full p-3 border border-neutral-300 rounded-lg text-sm focus:outline-none focus:border-neutral-500" placeholder="e.g. Data analyst internship, Jan intake" />
            </label>
            <div className="mt-5 flex justify-end gap-2">
              <button onClick={() => setInterest(null)} className="h-10 px-4 rounded-full border border-neutral-200 text-sm font-semibold hover:bg-neutral-50">Cancel</button>
              <button onClick={confirmInterest} className="h-10 px-5 rounded-full bg-neutral-900 text-white text-sm font-semibold hover:bg-black">Send to Mereka</button>
            </div>
          </div>
        </div>
      )}
    </PublicShell>
  );
}

/* ---------- Learner: consent toggle (EPIC D1) ----------
   Rendered on the learner dashboard. Default is off for anyone who has not
   been asked — consent is opt-in and timestamped. */
function TalentConsentCard({ profileId }) {
  useTalentState();
  const on = (typeof useFeature === 'function') ? useFeature('talent') : true;
  const p = TALENT_PROFILES.filter(x => x.id === (profileId || 't01'))[0] || TALENT_PROFILES[0];
  const given = talentConsent(p);
  if (!on) return null;
  return (
    <div className="bg-white border border-neutral-200 rounded-2xl p-5 mb-5">
      <div className="flex items-start gap-4 flex-wrap">
        <span className={`w-11 h-11 rounded-xl flex items-center justify-center flex-shrink-0 ${given ? 'bg-green-50 text-green-700' : 'bg-neutral-100 text-neutral-500'}`}><Icon name="shield" size={20} /></span>
        <div className="flex-1 min-w-[240px]">
          <h3 className="font-bold text-neutral-900">Share my profile with employers</h3>
          <p className="text-[13px] text-neutral-500 mt-1 leading-relaxed">
            Lets Mereka include your name, institution, certifications, skills and availability in talent lists shared with partners like Talent Corp. Your email, phone and IC are never shared. You can turn this off at any time.
          </p>
          {given && talentConsentTs(p) && <p className="text-[12px] text-green-700 mt-2">Consent given {String(talentConsentTs(p)).slice(0, 10)}</p>}
        </div>
        <button onClick={() => setTalentConsent(p.id, !given)} role="switch" aria-checked={given} aria-label="Share my profile with employers"
          className={`w-12 h-7 rounded-full transition-colors flex-shrink-0 relative ${given ? 'bg-green-600' : 'bg-neutral-300'}`}>
          <span className={`absolute top-1 w-5 h-5 rounded-full bg-white transition-all ${given ? 'left-6' : 'left-1'}`} />
        </button>
      </div>
    </div>
  );
}
