// HelperMatch Admin — Phase B1: Candidate review

// Mock data for work history + skills (until real data is wired)
const MOCK_WORK_HISTORY = [
  { from: '2023.03', to: '2026.02', years: 3, role: '家務管家', city: '香港', flagCode: 'HK', household: '4 人家庭，2 位兒童', endReason: '合約期滿', hasReference: true },
  { from: '2020.06', to: '2023.02', years: 2.7, role: '長輩照護', city: '新加坡', flagCode: 'SG', household: '夫妻與 1 位長輩', endReason: '雇主移居海外', hasReference: true },
  { from: '2018.08', to: '2020.05', years: 1.8, role: '家務助理', city: 'Jakarta', flagCode: 'ID', household: '3 人家庭', endReason: '家人生病返國', hasReference: false },
];

const DOC_CATEGORIES = [
  { key:'identity',   label:'身分證件',       icon:'idCard',   required: true },
  { key:'passport',   label:'護照',           icon:'passport', required: true },
  { key:'medical',    label:'醫療 / 體檢',    icon:'heart',    required: true },
  { key:'license',    label:'證照與執照',     icon:'award',    required: false },
  { key:'education',  label:'學歷證明',       icon:'book',     required: false },
  { key:'reference',  label:'推薦信 / 雇主證明', icon:'mail',   required: false },
  { key:'other',      label:'其他文件',       icon:'paperclip',required: false },
];

const MOCK_DOCUMENTS = [
  { id:'doc_001', category:'identity',  name:'KTP_front.jpg',                 type:'身分證正面',           ext:'jpg', sizeKb: 1240, uploadedAt: new Date(Date.now()-86400000*14), status:'VERIFIED', verifiedBy:'Anita Wijaya', verifiedAt: new Date(Date.now()-86400000*13), expiresAt:'2029-08-12', pages:1 },
  { id:'doc_002', category:'identity',  name:'KTP_back.jpg',                  type:'身分證反面',           ext:'jpg', sizeKb:  980, uploadedAt: new Date(Date.now()-86400000*14), status:'VERIFIED', verifiedBy:'Anita Wijaya', verifiedAt: new Date(Date.now()-86400000*13), expiresAt:'2029-08-12', pages:1 },
  { id:'doc_003', category:'passport',  name:'Passport_bio_page.pdf',         type:'護照個資頁',           ext:'pdf', sizeKb: 2420, uploadedAt: new Date(Date.now()-86400000*12), status:'VERIFIED', verifiedBy:'Anita Wijaya', verifiedAt: new Date(Date.now()-86400000*11), issuedAt:'2022-03-04', expiresAt:'2032-03-03', pages:2, note:'效期十年，符合多數雇主國要求' },
  { id:'doc_004', category:'medical',   name:'Medical_checkup_2026.pdf',      type:'海外工作體檢報告',     ext:'pdf', sizeKb: 3180, uploadedAt: new Date(Date.now()-86400000*8),  status:'VERIFIED', verifiedBy:'Dr. Hartono Clinic', verifiedAt: new Date(Date.now()-86400000*8), issuedAt:'2026-03-15', expiresAt:'2026-09-15', pages:6, note:'SG/HK/TW 通用格式' },
  { id:'doc_005', category:'medical',   name:'Vaccination_record.pdf',        type:'疫苗接種紀錄',         ext:'pdf', sizeKb: 1420, uploadedAt: new Date(Date.now()-86400000*8),  status:'VERIFIED', verifiedBy:'Dr. Hartono Clinic', verifiedAt: new Date(Date.now()-86400000*7), pages:3 },
  { id:'doc_006', category:'license',   name:'Caregiver_cert_BNP2TKI.pdf',    type:'BNP2TKI 看護認證',     ext:'pdf', sizeKb: 890,  uploadedAt: new Date(Date.now()-86400000*20), status:'VERIFIED', verifiedBy:'Anita Wijaya', verifiedAt: new Date(Date.now()-86400000*19), issuedAt:'2023-06-10', pages:1 },
  { id:'doc_007', category:'license',   name:'SIM_A_driving.jpg',             type:'印尼 A 級駕照',        ext:'jpg', sizeKb: 760,  uploadedAt: new Date(Date.now()-86400000*20), status:'PENDING',  expiresAt:'2028-05-22', pages:1 },
  { id:'doc_008', category:'license',   name:'First_aid_certificate.pdf',     type:'CPR / 急救證書',       ext:'pdf', sizeKb: 1120, uploadedAt: new Date(Date.now()-86400000*6),  status:'PENDING',  issuedAt:'2026-02-20', expiresAt:'2028-02-20', pages:2 },
  { id:'doc_009', category:'education', name:'Senior_high_diploma.pdf',       type:'高中畢業證書',         ext:'pdf', sizeKb: 1860, uploadedAt: new Date(Date.now()-86400000*22), status:'VERIFIED', verifiedBy:'Anita Wijaya', verifiedAt: new Date(Date.now()-86400000*21), pages:1 },
  // MVP: helper-uploaded reference letters are out of scope. The 'reference'
  // doc category is preserved in DOC_CATEGORIES so the schema is ready when the
  // workflow returns; we just don't seed any reference docs in mock data.
  { id:'doc_012', category:'other',     name:'Bank_account_info.pdf',         type:'銀行帳戶證明',         ext:'pdf', sizeKb: 420,  uploadedAt: new Date(Date.now()-86400000*10), status:'VERIFIED', verifiedBy:'Anita Wijaya', verifiedAt: new Date(Date.now()-86400000*9), pages:1 },
];

// Single profile photo preview. Production: read c.photoUrl directly from
// the candidate's primary CandidatePhoto row. Prototype: derive a slug
// from the name → match a file under Frontend/avatars/, fall back to a
// coloured initials tile if no file exists. Helpers can only upload one
// photo (the per-candidate count was removed).
const PhotoPreview = ({ c, size = 140 }) => {
  const slug = (c.name || '').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
  const url = c.photoUrl || (slug ? `../Frontend/avatars/${slug}.jpg` : '');
  const initials = (c.name || '?').split(/\s+/).filter(Boolean).map(p => p[0]).join('').slice(0, 2).toUpperCase();
  // Deterministic colour from the candidate id so the fallback is stable.
  const seed = String(c.id || c.name || '').split('').reduce((s, ch) => s + ch.charCodeAt(0), 0);
  const hue = seed % 360;
  const [errored, setErrored] = uS(false);
  const showImg = url && !errored;
  return (
    <div
      style={{
        width: size, height: size,
        borderRadius: 12,
        background: showImg ? '#f3f4f6' : `linear-gradient(135deg, hsl(${hue},55%,68%), hsl(${(hue+30)%360},45%,52%))`,
        display: 'grid', placeItems: 'center',
        color: '#fff', fontWeight: 700, fontSize: size * 0.32, letterSpacing: '-0.02em',
        overflow: 'hidden', flexShrink: 0,
        border: '1px solid var(--a-line)',
        position: 'relative',
      }}
    >
      {showImg ? (
        <img
          src={url}
          alt={c.name}
          style={{ width: '100%', height: '100%', objectFit: 'cover' }}
          onError={() => setErrored(true)}
        />
      ) : initials}
    </div>
  );
};

// Demo subset of the front-end SKILL_TAXONOMY — used when the admin
// candidate record doesn't carry typedSkills yet (which is most of them in
// the prototype). Production: read directly from `c.typedSkills` populated
// by the resume form (PRODUCTION_TODO.md → P0 #4).
const _pickFromTaxonomy = (cat, ids) => {
  const tax = (window.SKILL_TAXONOMY && window.SKILL_TAXONOMY[cat]) || [];
  return tax.filter(x => ids.includes(x.id));
};
const getDemoSkills = () => ({
  languages: _pickFromTaxonomy('languages', ['english', 'mandarin', 'cantonese', 'indonesian']),
  mainSkills: _pickFromTaxonomy('mainSkills', ['baby', 'child', 'elderly', 'housekeeping', 'cooking']),
  cookingSkills: _pickFromTaxonomy('cookingSkills', ['asian', 'chinese', 'western']),
  otherSkills: _pickFromTaxonomy('otherSkills', ['first-aid', 'caregiver', 'housework']),
  personality: _pickFromTaxonomy('personality', ['patient', 'calm', 'reliable', 'warm', 'cheerful', 'organized']),
});

// Color tokens mirror Frontend/components.jsx SKILL_CAT_COLORS but inline as
// hex (admin's stylesheet doesn't define the front-end CSS vars). Keeps
// the visual treatment consistent across the candidate-side single helper
// page and the admin candidate drawer.
const SKILL_CAT_COLORS_INLINE = {
  language:    { bg: '#FBE2D5', fg: '#7E2A14' },
  personality: { bg: '#FBE9C5', fg: '#8A5A12' },
  main:        { bg: '#D5F0E8', fg: '#1B6D5C' },
  cooking:     { bg: '#FBD9C3', fg: '#B85A2C' },
  other:       { bg: '#E5E0F0', fg: '#5A4B86' },
};

// Per-category render — mirrors Frontend SkillsGroup styling:
//   language : filled coral pill + flag glyph
//   cooking  : filled coral pill (no icon)
//   main / other / personality : white pill with mini colored disc + label
// Differs from front-end only in that the disc is a solid colored circle
// (admin doesn't load components.jsx where SkillIcon lives — keeping the
// admin / front-end JS surfaces decoupled is intentional).
const SkillBlock = ({ title, items, required, category = 'main', full }) => {
  const c = SKILL_CAT_COLORS_INLINE[category] || SKILL_CAT_COLORS_INLINE.main;
  const lang = (window.i18n && window.i18n.lang) || 'zh-TW';
  const labelOf = (x) => (window.pickLocalised ? window.pickLocalised(x, 'label', lang) : null) || x.label || x.id;
  if (!items || items.length === 0) return null;
  return (
    <div style={full ? {gridColumn:'1 / -1'} : undefined}>
      <div className="flex items-baseline gap-8 mb-10" style={{paddingBottom:8,borderBottom:'1px solid var(--a-line)'}}>
        <span className="fs-12 fw-700" style={{letterSpacing:'0.04em',textTransform:'uppercase'}}>{title}</span>
        {required && <span className="fs-11 text-muted">· 必填</span>}
        <span className="fs-11 text-muted ml-auto">{items.length} 項</span>
      </div>
      <div className="flex flex-wrap" style={{gap:6}}>
        {items.map(x => {
          if (category === 'cooking') {
            return (
              <span key={x.id} style={{display:'inline-flex', padding:'6px 12px', borderRadius:99, background:c.bg, color:c.fg, fontSize:12.5, fontWeight:500}}>
                {labelOf(x)}
              </span>
            );
          }
          if (category === 'language') {
            return (
              <span key={x.id} style={{display:'inline-flex', alignItems:'center', gap:6, padding:'6px 12px', borderRadius:99, background:c.bg, color:c.fg, fontSize:12.5, fontWeight:500}}>
                {x.flag && <span style={{fontSize:14, lineHeight:1}}>{x.flag}</span>}
                {labelOf(x)}
              </span>
            );
          }
          return (
            <div key={x.id} style={{display:'inline-flex', alignItems:'center', gap:7, padding:'5px 12px 5px 5px', background:'#fff', border:'1px solid var(--a-line)', borderRadius:99, fontSize:13}}>
              <span style={{display:'inline-block', width:18, height:18, borderRadius:99, background:c.bg, flexShrink:0}}/>
              <span style={{fontWeight:500, whiteSpace:'nowrap', color:'var(--a-ink)'}}>{labelOf(x)}</span>
            </div>
          );
        })}
      </div>
    </div>
  );
};

const CandidatesPageReal = () => {
  const { t } = (window.useI18n ? window.useI18n() : { t: (k)=>k });
  const { navigate, route } = useRoute();
  const { user } = useAuth();
  const toast = useToast();
  const isAssessor = user.role === 'ASSESSOR';
  const [tab, setTab] = uS(() => {
    const m = (window.location.hash.match(/status=([A-Z_]+)/));
    return m ? m[1] : 'PENDING_REVIEW';
  });
  const [q, setQ] = uS('');
  const [natFilter, setNatFilter] = uS('');
  const [marketFilter, setMarketFilter] = uS('');
  const [sortBy, setSortBy] = uS('oldest');
  const [openId, setOpenId] = uS(null);
  const [cands, setCands] = uS(() => ADMIN.candidates.map(c => ({ ...c })));

  // Guard: assessor can never see ACTIVE/ALL tabs
  useEffect(() => {
    if (isAssessor && (tab === 'ACTIVE' || tab === 'ALL')) setTab('PENDING_REVIEW');
  }, [isAssessor, tab]);

  const tabs = isAssessor ? [
    { key: 'PENDING_REVIEW', label: '待審核' },
    { key: 'REJECTED', label: '已駁回' },
    { key: 'SUSPENDED', label: '停權' },
  ] : [
    { key: 'PENDING_REVIEW', label: '待審核' },
    { key: 'ACTIVE', label: '已上架' },
    { key: 'REJECTED', label: '已駁回' },
    { key: 'SUSPENDED', label: '停權' },
    { key: 'ALL', label: '全部' },
  ];
  const tabCount = (k) => k === 'ALL' ? cands.length : cands.filter(c => c.status === k).length;

  const filtered = uM(() => {
    let list = cands;
    // Assessors never see ACTIVE candidates
    if (isAssessor) list = list.filter(c => c.status !== 'ACTIVE');
    if (tab !== 'ALL') list = list.filter(c => c.status === tab);
    if (q) {
      const qL = q.toLowerCase();
      list = list.filter(c => c.name.toLowerCase().includes(qL) || c.id.includes(qL) || c.city.toLowerCase().includes(qL));
    }
    if (natFilter) list = list.filter(c => c.nationality === natFilter);
    if (marketFilter) list = list.filter(c => c.targetMarkets.includes(marketFilter));
    list = [...list];
    if (sortBy === 'oldest') list.sort((a,b) => new Date(a.submittedAt||a.createdAt) - new Date(b.submittedAt||b.createdAt));
    if (sortBy === 'newest') list.sort((a,b) => new Date(b.submittedAt||b.createdAt) - new Date(a.submittedAt||a.createdAt));
    if (sortBy === 'completeness') list.sort((a,b) => b.completeness - a.completeness);
    return list;
  }, [cands, tab, q, natFilter, marketFilter, sortBy]);

  const updateCand = (id, patch, logAction, logDiff, logReason) => {
    setCands(cs => cs.map(c => c.id === id ? { ...c, ...patch } : c));
    if (logAction) {
      ADMIN.auditLogs.unshift({
        id: 'log_' + Math.random().toString(36).slice(2,8),
        at: new Date(), actor: (window.__currentAdminName || 'System'), action: logAction,
        targetType: 'Candidate', targetId: id,
        diff: { ...logDiff, ...(logReason ? { reason: logReason } : {}) }
      });
    }
  };

  const openCand = cands.find(c => c.id === openId);

  return (
    <div className="a-page a-page-wide">
      <div className="a-page-header">
        <div>
              <h1 className="a-page-title">{t('admin.candidates.title')}</h1>
    <p className="a-page-sub">{t('admin.candidates.subtitle')}</p>
        </div>
        <div className="a-page-header-actions">
          {/* CSV export — Super Admin only (data egress / privacy gate). */}
          {isSuperAdmin(user) && <button className="a-btn a-btn-default"><AIcon name="download" size={14}/> {t('admin.common.exportCsv')}</button>}
          <button className="a-btn a-btn-primary"><AIcon name="plus" size={14}/> 新增候選人</button>
        </div>
      </div>

      <div className="a-tabs">
        {tabs.map(t => (
          <button key={t.key} className={`a-tab ${tab===t.key?'active':''}`} onClick={()=>setTab(t.key)}>
            {t.label} <span className="a-tab-count">{tabCount(t.key)}</span>
          </button>
        ))}
      </div>

      <div className="a-card">
        <div className="a-table-toolbar">
          <div className="a-input-group" style={{width:260}}>
            <span className="a-input-group-prefix"><AIcon name="search" size={14}/></span>
            <input className="a-input" placeholder="搜尋姓名、ID、城市…" value={q} onChange={e=>setQ(e.target.value)}/>
          </div>
          <select className="a-select" style={{width:130}} value={natFilter} onChange={e=>setNatFilter(e.target.value)}>
            <option value="">所有國籍</option>
            {Object.entries(ADMIN.nat).map(([k,v]) => <option key={k} value={k}>{v.flag} {v.name}</option>)}
          </select>
          <select className="a-select" style={{width:130}} value={marketFilter} onChange={e=>setMarketFilter(e.target.value)}>
            <option value="">所有市場</option>
            <option value="TW">🇹🇼 台灣</option><option value="HK">🇭🇰 香港</option><option value="SG">🇸🇬 新加坡</option>
          </select>
          <select className="a-select" style={{width:150}} value={sortBy} onChange={e=>setSortBy(e.target.value)}>
            <option value="oldest">送審最久 →</option>
            <option value="newest">送審最新 →</option>
            <option value="completeness">完整度 →</option>
          </select>
          <span className="ml-auto text-muted fs-13">共 {filtered.length} 位</span>
        </div>

        <table className="a-table">
          <thead><tr>
            <th>候選人</th>
            <th>國籍 / 城市</th>
            <th>狀態</th>
            <th style={{width:150}}>完整度</th>
            <th>{tab==='PENDING_REVIEW' ? '送審時間' : '最後更新'}</th>
            <th></th>
          </tr></thead>
          <tbody>
            {filtered.map(c => (
              <tr key={c.id} className="clickable" onClick={()=>setOpenId(c.id)}>
                <td><CandidateCell c={c} sub={c.resubmit ? '⟲ 重新送審' : c.id}/></td>
                <td>
                  <div className="flex items-center gap-6">
                    <Flag code={c.nationality} size={14}/>
                    <span className="fs-13">{c.city}</span>
                  </div>
                  <div className="text-muted fs-12">{c.age}歲 · {c.gender==='F'?'女':'男'}</div>
                </td>
                <td>
                  <div className="flex items-center gap-6">
                    <StatusBadge status={c.status}/>
                    {c.verification && <VerificationChip v={c.verification}/>}
                  </div>
                </td>
                <td>
                  <div className="flex items-center gap-8">
                    <div className="a-progress" style={{flex:1,height:6}}>
                      <div className="a-progress-bar" style={{width:c.completeness+'%', background: c.completeness >= 90 ? 'var(--a-success)' : c.completeness >= 75 ? 'var(--a-primary)' : 'var(--a-warn)'}}/>
                    </div>
                    <span className="fs-12 fw-600 tnum">{c.completeness}%</span>
                  </div>
                </td>
                <td className="text-muted">{FMT.rel(c.submittedAt || c.createdAt)}</td>
                <td><AIcon name="chevRight" size={13} className="text-faint"/></td>
              </tr>
            ))}
            {filtered.length === 0 && <tr><td colSpan={6}><AEmpty title="沒有符合條件的候選人" sub="調整篩選或搜尋條件試試。"/></td></tr>}
          </tbody>
        </table>
      </div>

      {openCand && <CandidateDrawer c={openCand} onClose={()=>setOpenId(null)} onUpdate={updateCand}/>}
    </div>
  );
};

// ========= Candidate drawer =========
const CandidateDrawer = ({ c, onClose, onUpdate }) => {
  const toast = useToast();
  const { user } = useAuth();
  const canEditResume = user && (user.role === 'SUPER_ADMIN' || user.role === 'OPS_MANAGER');
  const [tab, setTab] = uS('profile');
  const [showApprove, setShowApprove] = uS(false);
  const [showReject, setShowReject] = uS(false);
  const [showChanges, setShowChanges] = uS(false);
  const [showSuspend, setShowSuspend] = uS(false);
  const [showEditResume, setShowEditResume] = uS(false);

  const asm = ADMIN.assessments.find(a => a.candidateId === c.id);
  const videos = ADMIN.videos.filter(v => v.candidateId === c.id);
  const refsAsReferrer = ADMIN.referrals.filter(r => r.referrerId === c.id);
  const refsAsReferee = ADMIN.referrals.filter(r => r.refereeId === c.id);

  return (
    <ADrawer open onClose={onClose} size="xl" title={
      <span style={{display:'flex',alignItems:'center',gap:10}}>
        <AAvatar name={c.name} id={c.id} size={32} square/>
        <span>
          <span style={{fontSize:15,fontWeight:600}}>{c.name}</span>
          <span className="text-muted fs-12" style={{marginLeft:8}}><Mono>{c.id}</Mono></span>
        </span>
        <StatusBadge status={c.status}/>
        {c.tier && <Tier t={c.tier}/>}
        {c.verification && <VerificationChip v={c.verification}/>}
      </span>
    } footer={
      c.status === 'PENDING_REVIEW' ? (
        <>
          <button className="a-btn a-btn-danger" onClick={()=>setShowReject(true)}><AIcon name="xCircle" size={13}/> 駁回</button>
          <button className="a-btn a-btn-default" onClick={()=>setShowChanges(true)}><AIcon name="edit" size={13}/> 要求補件</button>
          <div style={{flex:1}}/>
          <button className="a-btn a-btn-ghost" onClick={onClose}>關閉</button>
          <button className="a-btn a-btn-success-solid" onClick={()=>setShowApprove(true)}><AIcon name="check" size={13}/> 通過並上架</button>
        </>
      ) : c.status === 'ACTIVE' ? (
        <>
          <button className="a-btn a-btn-danger" onClick={()=>setShowSuspend(true)}><AIcon name="ban" size={13}/> 停權</button>
          <div style={{flex:1}}/>
          <button className="a-btn a-btn-ghost" onClick={onClose}>關閉</button>
          <button className="a-btn a-btn-default"><AIcon name="edit" size={13}/> 編輯資料</button>
        </>
      ) : (
        <>
          <div style={{flex:1}}/>
          <button className="a-btn a-btn-ghost" onClick={onClose}>關閉</button>
          {(c.status === 'REJECTED' || c.status === 'SUSPENDED') && (
            <button className="a-btn a-btn-default" onClick={()=>{
              onUpdate(c.id, { status:'ACTIVE', rejectReason:null, suspendReason:null }, 'CANDIDATE_UNSUSPEND',
                { status:{ before:c.status, after:'ACTIVE' } });
              toast('已恢復上架', 'success'); onClose();
            }}>恢復上架</button>
          )}
        </>
      )
    }>
      {/* Tabs.
          Documents (文件) and Assessment (評測) are temporarily hidden from
          the tab strip — they need backend support that isn't built yet
          (document storage + verifier flow, in-person assessment scoring).
          The CandDocumentsTab + CandAssessmentTab components are kept
          defined below and the render guards stay in place so re-enabling
          is a matter of adding the entries back to this array.
          See PRODUCTION_TODO.md → P1 Verification & Documents block. */}
      <div className="a-tabs" style={{marginBottom:16}}>
        {[
          { k:'profile', label:'基本資料' },
          // { k:'documents', label:'文件', n: (c.documents || MOCK_DOCUMENTS).length },
          // { k:'assessment', label:'評測', n: asm ? 1 : 0 },
          { k:'videos', label:'影片', n: videos.length },
          { k:'referrals', label:'Referral', n: refsAsReferrer.length + refsAsReferee.length },
          { k:'history', label:'審核歷程' },
        ].map(t => (
          <button key={t.k} className={`a-tab ${tab===t.k?'active':''}`} onClick={()=>setTab(t.k)}>
            {t.label} {t.n !== undefined && t.n > 0 && <span className="a-tab-count">{t.n}</span>}
          </button>
        ))}
      </div>

      {tab === 'profile' && <CandProfileTab c={c} canEditResume={canEditResume} onEditResume={()=>setShowEditResume(true)}/>}
      {tab === 'documents' && <CandDocumentsTab c={c}/>}
      {tab === 'assessment' && <CandAssessmentTab asm={asm} c={c}/>}
      {tab === 'videos' && <CandVideosTab videos={videos}/>}
      {tab === 'referrals' && <CandReferralsTab asReferrer={refsAsReferrer} asReferee={refsAsReferee}/>}
      {tab === 'history' && <CandHistoryTab c={c}/>}

      {/* Approve modal */}
      <AModal open={showApprove} onClose={()=>setShowApprove(false)} title="通過並上架候選人"
        icon={<span style={{color:'var(--a-success)'}}><AIcon name="checkCircle" size={20}/></span>}
        footer={<>
          <button className="a-btn a-btn-ghost" onClick={()=>setShowApprove(false)}>取消</button>
          <button className="a-btn a-btn-success-solid" onClick={()=>{
            onUpdate(c.id, { status:'ACTIVE', reviewedAt: new Date() }, 'CANDIDATE_APPROVE',
              { status:{ before:'PENDING_REVIEW', after:'ACTIVE' } });
            // Demo bridge: also flip the front-end localStorage flag so a
            // helper dashboard open in another tab updates live (see
            // Shared/CANDIDATE_REVIEW_FLOW.md → Prototype implementation).
            try {
              localStorage.setItem('hm_review_status', 'approved');
              localStorage.removeItem('hm_review_reject_reason');
              localStorage.removeItem('hm_review_approved_seen');
              window.dispatchEvent(new Event('hm-review-status-changed'));
            } catch(e) {}
            toast(`${c.name} 已通過並上架`, 'success'); setShowApprove(false); onClose();
          }}>確認通過</button>
        </>}>
        <p className="fs-14 mb-16">將 <b>{c.name}</b> 的狀態從 <StatusBadge status="PENDING_REVIEW"/> 改為 <StatusBadge status="ACTIVE"/>？</p>
        <div style={{padding:14,background:'var(--a-success-bg)',borderRadius:8,border:'1px solid var(--a-success-line)'}}>
          <div className="fw-600 fs-13 mb-6" style={{color:'var(--a-success)'}}>上架後將：</div>
          <ul className="fs-13" style={{margin:0,paddingLeft:18,color:'var(--a-ink-soft)',lineHeight:1.7}}>
            <li>公開顯示於 {c.targetMarkets.join(' / ')} 市場的搜尋結果</li>
            <li>Referrer（如有）的 Gate 1 判定生效</li>
            <li>候選人將收到 email 通知</li>
          </ul>
        </div>
        <div className="mt-14">
          <label className="a-label">初始 Tier <span className="a-label-hint">可稍後在評測完成時調整</span></label>
          <div className="a-radio-pill-group">
            {['A','B','C','D'].map(t => (
              <label key={t} className={`a-radio-pill ${(c.tier||'B')===t?'active':''}`}><input type="radio" name="tier" defaultChecked={t==='B'} style={{display:'none'}}/>{t}</label>
            ))}
          </div>
        </div>
      </AModal>

      {/* Reject modal */}
      <RejectModal open={showReject} onClose={()=>setShowReject(false)} c={c} onConfirm={(reason) => {
        onUpdate(c.id, { status:'REJECTED', rejectReason: reason }, 'CANDIDATE_REJECT',
          { status:{ before:'PENDING_REVIEW', after:'REJECTED' } }, reason);
        // Demo bridge — see CANDIDATE_REVIEW_FLOW.md.
        try {
          localStorage.setItem('hm_review_status', 'rejected');
          if (reason) localStorage.setItem('hm_review_reject_reason', reason);
          window.dispatchEvent(new Event('hm-review-status-changed'));
        } catch(e) {}
        toast(`已駁回 ${c.name}`, 'danger'); setShowReject(false); onClose();
      }}/>
      <ChangesModal open={showChanges} onClose={()=>setShowChanges(false)} c={c} onConfirm={(note) => {
        ADMIN.auditLogs.unshift({ id:'log_'+Math.random().toString(36).slice(2,8), at:new Date(), actor:(window.__currentAdminName || 'System'),
          action:'CANDIDATE_REQUEST_CHANGES', targetType:'Candidate', targetId:c.id, diff:{ reviewNote:note }});
        toast(`已要求 ${c.name} 補件`, 'warn'); setShowChanges(false); onClose();
      }}/>
      <SuspendModal open={showSuspend} onClose={()=>setShowSuspend(false)} c={c} onConfirm={(reason)=>{
        onUpdate(c.id, { status:'SUSPENDED', suspendReason:reason }, 'CANDIDATE_SUSPEND',
          { status:{ before:'ACTIVE', after:'SUSPENDED' } }, reason);
        toast(`已停權 ${c.name}`, 'danger'); setShowSuspend(false); onClose();
      }}/>
      {showEditResume && canEditResume && (
        <EditResumeDrawer c={c} onClose={()=>setShowEditResume(false)} onSave={(patch, diff) => {
          onUpdate(c.id, patch, 'CANDIDATE_RESUME_EDIT', diff);
          toast(`已更新 ${c.name} 的資料`, 'success'); setShowEditResume(false);
        }}/>
      )}
    </ADrawer>
  );
};

// --- Edit Resume drawer (SUPER_ADMIN / OPS_MANAGER only) ---
// Inline-editable subset of the candidate profile. Compares each field to the
// original and emits a diff so the audit log reflects exactly what changed.
const EditResumeDrawer = ({ c, onClose, onSave }) => {
  const [form, setForm] = uS({
    name:           c.name           ?? '',
    age:            c.age            ?? '',
    gender:         c.gender         ?? 'F',
    nationality:    c.nationality    ?? 'ID',
    city:           c.city           ?? '',
    height:         c.height         ?? '',
    weight:         c.weight         ?? '',
    religion:       c.religion       ?? '',
    education:      c.education      ?? '',
    contractStatus: c.contractStatus ?? 'FIRST_TIME',
    availableDate:  c.availableDate  ?? '',
    targetMarkets:  c.targetMarkets  ?? [],
    bioZh:          c.bioZh          ?? '',
    bioEn:          c.bioEn          ?? '',
    bioId:          c.bioId          ?? '',
    verification:   c.verification   ?? null,
    agencyName:     c.agencyName     ?? '',
  });

  const set = (k, v) => setForm(f => ({ ...f, [k]: v }));
  const toggleMarket = (m) => set('targetMarkets',
    form.targetMarkets.includes(m) ? form.targetMarkets.filter(x => x !== m) : [...form.targetMarkets, m]);

  const handleSave = () => {
    // Build a minimal patch + audit diff from changed fields only.
    const patch = {};
    const diff = {};
    Object.keys(form).forEach(k => {
      const before = c[k];
      const after  = form[k];
      const same = Array.isArray(before) && Array.isArray(after)
        ? before.length === after.length && before.every((v,i) => v === after[i])
        : (before ?? null) === (after ?? null);
      if (!same) {
        patch[k] = (k === 'age') ? (Number(after) || null) : after;
        diff[k] = { before: before ?? null, after: patch[k] };
      }
    });
    if (Object.keys(patch).length === 0) { onClose(); return; }
    onSave(patch, diff);
  };

  const ALL_MARKETS = ['TW','HK','SG','MY','MO','UAE','SA'];
  return (
    <ADrawer open onClose={onClose} size="lg" title={
      <span style={{display:'flex',alignItems:'center',gap:10}}>
        <AIcon name="edit" size={16}/>
        <span>編輯履歷 — <span className="text-muted fw-500">{c.name}</span></span>
      </span>
    } footer={<>
      <button className="a-btn a-btn-ghost" onClick={onClose}>取消</button>
      <div style={{flex:1}}/>
      <button className="a-btn a-btn-primary" onClick={handleSave}><AIcon name="check" size={13}/> 儲存變更</button>
    </>}>
      {/* Verification */}
      <div className="a-card mb-16">
        <div className="a-card-header"><h3 className="a-card-title">驗證方式</h3></div>
        <div className="a-card-body">
          <label className="a-label">來源 *</label>
          <div className="a-radio-pill-group" style={{flexWrap:'wrap'}}>
            {[
              { v:'official', label:'官方評測 (Official)' },
              { v:'agency',   label:'仲介背書 (Agency)' },
              { v:'self',     label:'自我登記 (Self)' },
              { v:null,       label:'未驗證' },
            ].map(o => (
              <label key={o.label} className={`a-radio-pill ${form.verification===o.v?'active':''}`}
                onClick={()=>set('verification', o.v)}>
                <input type="radio" name="verification" checked={form.verification===o.v} readOnly style={{display:'none'}}/>
                {o.label}
              </label>
            ))}
          </div>
          {form.verification === 'agency' && (
            <div className="mt-12">
              <label className="a-label">仲介名稱 *</label>
              <input className="a-input" value={form.agencyName} onChange={e=>set('agencyName', e.target.value)} placeholder="例：PT Mitra Sejahtera"/>
            </div>
          )}
        </div>
      </div>

      {/* Personal info */}
      <div className="a-card mb-16">
        <div className="a-card-header"><h3 className="a-card-title">個人資訊</h3></div>
        <div className="a-card-body">
          <div className="grid-2" style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:12}}>
            <div><label className="a-label">姓名</label><input className="a-input" value={form.name} onChange={e=>set('name', e.target.value)}/></div>
            <div>
              <label className="a-label">國籍</label>
              <select className="a-select" value={form.nationality} onChange={e=>set('nationality', e.target.value)}>
                <option value="ID">🇮🇩 印尼</option>
                <option value="PH">🇵🇭 菲律賓</option>
                <option value="VN">🇻🇳 越南</option>
                <option value="TH">🇹🇭 泰國</option>
              </select>
            </div>
            <div><label className="a-label">年齡</label><input className="a-input" type="number" value={form.age} onChange={e=>set('age', e.target.value)}/></div>
            <div>
              <label className="a-label">性別</label>
              <select className="a-select" value={form.gender} onChange={e=>set('gender', e.target.value)}>
                <option value="F">女</option><option value="M">男</option>
              </select>
            </div>
            <div><label className="a-label">城市</label><input className="a-input" value={form.city} onChange={e=>set('city', e.target.value)}/></div>
            <div><label className="a-label">宗教</label><input className="a-input" value={form.religion} onChange={e=>set('religion', e.target.value)}/></div>
            <div><label className="a-label">身高 (cm)</label><input className="a-input" type="number" value={form.height} onChange={e=>set('height', Number(e.target.value)||'')}/></div>
            <div><label className="a-label">體重 (kg)</label><input className="a-input" type="number" value={form.weight} onChange={e=>set('weight', Number(e.target.value)||'')}/></div>
            <div style={{gridColumn:'1 / -1'}}>
              <label className="a-label">教育</label>
              <select className="a-select" value={form.education} onChange={e=>set('education', e.target.value)}>
                <option value="">未填</option>
                <option>Middle School</option>
                <option>High School</option>
                <option>Diploma</option>
                <option>Bachelors</option>
              </select>
            </div>
          </div>
        </div>
      </div>

      {/* Work preferences */}
      <div className="a-card mb-16">
        <div className="a-card-header"><h3 className="a-card-title">工作偏好</h3></div>
        <div className="a-card-body">
          <label className="a-label">目標市場（可複選）</label>
          <div className="flex flex-wrap gap-6 mb-12">
            {ALL_MARKETS.map(m => (
              <button key={m} type="button"
                className={`a-btn a-btn-sm ${form.targetMarkets.includes(m) ? 'a-btn-primary' : 'a-btn-default'}`}
                onClick={()=>toggleMarket(m)}>{m}</button>
            ))}
          </div>
          <div className="grid-2" style={{display:'grid',gridTemplateColumns:'1fr 1fr',gap:12}}>
            <div>
              <label className="a-label">合約狀態</label>
              <select className="a-select" value={form.contractStatus} onChange={e=>set('contractStatus', e.target.value)}>
                <option value="FIRST_TIME">首次出國</option>
                <option value="TRANSFER">轉換僱主</option>
                <option value="FINISHED">已完約</option>
                <option value="BREAK_CONTRACT">中斷合約</option>
                <option value="IN_HOME_COUNTRY">在母國</option>
              </select>
            </div>
            <div>
              <label className="a-label">可開始日期</label>
              <input className="a-input" type="date" value={form.availableDate} onChange={e=>set('availableDate', e.target.value)}/>
            </div>
          </div>
        </div>
      </div>

      {/* Bio (three languages) */}
      <div className="a-card">
        <div className="a-card-header"><h3 className="a-card-title">自我介紹（三語言）</h3></div>
        <div className="a-card-body">
          <label className="a-label">🇹🇼 繁中</label>
          <textarea className="a-textarea" rows={3} value={form.bioZh} onChange={e=>set('bioZh', e.target.value)}/>
          <label className="a-label mt-12">🇬🇧 English</label>
          <textarea className="a-textarea" rows={3} value={form.bioEn} onChange={e=>set('bioEn', e.target.value)}/>
          <label className="a-label mt-12">🇮🇩 Indonesia</label>
          <textarea className="a-textarea" rows={3} value={form.bioId} onChange={e=>set('bioId', e.target.value)}/>
        </div>
      </div>
    </ADrawer>
  );
};

// --- Reject / Changes / Suspend modals share pattern ---
const RejectModal = ({ open, onClose, c, onConfirm }) => {
  const [reason, setReason] = uS('');
  const [cat, setCat] = uS('');
  const cats = ['資料不完整','照片品質不佳','年齡/條件不符','疑似詐欺','其他'];
  uE(() => { if (open) { setReason(''); setCat(''); } }, [open]);
  return (
    <AModal open={open} onClose={onClose} title="駁回候選人" icon={<span style={{color:'var(--a-danger)'}}><AIcon name="xCircle" size={20}/></span>}
      footer={<>
        <button className="a-btn a-btn-ghost" onClick={onClose}>取消</button>
        <button className="a-btn a-btn-danger-solid" disabled={!cat || !reason.trim()} onClick={()=>onConfirm(`[${cat}] ${reason.trim()}`)}>確認駁回</button>
      </>}>
      <p className="fs-14 mb-14">駁回 <b>{c.name}</b>。候選人會收到站內通知，可以修改資料後重新送審。</p>
      <label className="a-label">原因分類 *</label>
      <div className="flex flex-wrap gap-6 mb-14">
        {cats.map(x => (
          <button key={x} className={`a-btn a-btn-sm ${cat===x?'a-btn-primary':'a-btn-default'}`} onClick={()=>setCat(x)}>{x}</button>
        ))}
      </div>
      <label className="a-label">說明（候選人看得到）*</label>
      <textarea className="a-textarea" rows={4} placeholder="請詳述駁回原因，讓候選人知道如何改進…" value={reason} onChange={e=>setReason(e.target.value)}/>
    </AModal>
  );
};
const ChangesModal = ({ open, onClose, c, onConfirm }) => {
  const [note, setNote] = uS('');
  uE(() => { if (open) setNote(''); }, [open]);
  const presets = ['照片過小或模糊，請重傳','個人介紹過短，請補充至 150 字以上','需補齊過往工作經歷','身份證明文件未上傳'];
  return (
    <AModal open={open} onClose={onClose} title="要求補件" icon={<span style={{color:'var(--a-warn)'}}><AIcon name="edit" size={20}/></span>}
      footer={<>
        <button className="a-btn a-btn-ghost" onClick={onClose}>取消</button>
        <button className="a-btn a-btn-primary" disabled={!note.trim()} onClick={()=>onConfirm(note.trim())}>發送</button>
      </>}>
      <p className="fs-14 mb-14">要求 <b>{c.name}</b> 補件後重新送審。狀態維持 <StatusBadge status="PENDING_REVIEW"/>。</p>
      <div className="mb-10 fs-12 fw-600 text-muted">常用訊息：</div>
      <div className="flex flex-wrap gap-6 mb-14">
        {presets.map(p => <button key={p} className="a-btn a-btn-sm a-btn-default" onClick={()=>setNote(n => n ? n + '\n' + p : p)}>+ {p}</button>)}
      </div>
      <label className="a-label">詳細說明 *</label>
      <textarea className="a-textarea" rows={5} value={note} onChange={e=>setNote(e.target.value)} placeholder="說明需要補齊的資料…"/>
    </AModal>
  );
};
const SuspendModal = ({ open, onClose, c, onConfirm }) => {
  const [reason, setReason] = uS('');
  uE(() => { if (open) setReason(''); }, [open]);
  return (
    <AModal open={open} onClose={onClose} title="停權候選人" icon={<span style={{color:'var(--a-danger)'}}><AIcon name="ban" size={20}/></span>}
      footer={<>
        <button className="a-btn a-btn-ghost" onClick={onClose}>取消</button>
        <button className="a-btn a-btn-danger-solid" disabled={!reason.trim()} onClick={()=>onConfirm(reason.trim())}>確認停權</button>
      </>}>
      <p className="fs-14 mb-14">停權 <b>{c.name}</b>。候選人將從搜尋結果下架、無法接收新訊息。</p>
      <div style={{padding:12,background:'var(--a-danger-bg)',borderRadius:8,border:'1px solid var(--a-danger-line)'}} className="mb-14 fs-13">
        <b>⚠ 停權影響：</b>進行中的 Referral 將進入觀察審查、既有訊息保留不變。
      </div>
      <label className="a-label">停權原因 *</label>
      <textarea className="a-textarea" rows={4} value={reason} onChange={e=>setReason(e.target.value)}/>
    </AModal>
  );
};

// ========= Candidate drawer tabs =========
const VERIFICATION_LABEL = {
  official: '官方評測（HelperMatch Surabaya 評測站）',
  agency:   '仲介背書',
  self:     '自我登記（候選人本人）',
};
const VERIFICATION_HINT = {
  official: '此候選人已於泗水站完成標準化評測，是平台最高信任等級。',
  agency:   '由合作仲介代為登記與背書。仲介需對資料準確性負責。',
  self:     '由候選人本人自行註冊。資料準確性以候選人自我陳述為準。',
};

const CandProfileTab = ({ c, canEditResume, onEditResume }) => (
  <div className="grid-2 gap-16" style={{gridTemplateColumns:'1fr 1fr'}}>
    {/* Edit Resume action bar — visible to SUPER_ADMIN / OPS_MANAGER only */}
    {canEditResume && (
      <div style={{gridColumn:'1 / -1', display:'flex', justifyContent:'flex-end', marginBottom:-8}}>
        <button className="a-btn a-btn-default a-btn-sm" onClick={onEditResume}>
          <AIcon name="edit" size={13}/> 編輯履歷
        </button>
      </div>
    )}

    {/* Verification source card hidden — depends on a verification feature
        that hasn't shipped yet (PRODUCTION_TODO.md → P1 #14-#17). Restoring
        is a matter of un-commenting this block; VerificationChip /
        VERIFICATION_LABEL / VERIFICATION_HINT are kept defined below. */}

    {/* Photo — single primary photo (helpers can only upload one).
        See CANDIDATE_REVIEW_FLOW.md → admin reviewers use this preview
        when deciding approve / reject. */}
    <div className="a-card">
      <div className="a-card-header">
        <h3 className="a-card-title">個人照片</h3>
        <span className="text-muted fs-12 ml-auto">候選人僅能上傳 1 張</span>
      </div>
      <div className="a-card-body" style={{display:'flex', gap:18, alignItems:'flex-start'}}>
        <PhotoPreview c={c} size={140}/>
        <div style={{flex:1, minWidth:0}}>
          <div className="fs-13 fw-600 mb-4">{c.name}</div>
          <div className="fs-12 text-muted" style={{lineHeight:1.6}}>
            這張照片會顯示在公開的人選詳細頁與卡片列表上。<br/>
            審核時請確認：照片清晰、五官可辨識、無濾鏡或大頭貼。
          </div>
        </div>
      </div>
    </div>

    {/* Personal info — fields mirror the front-end resume form's "About" +
        "Background" steps (Frontend/pages-forms.jsx CreateProfilePage).
        Fields not yet on the admin candidate record (marital, kids, contact,
        years experience) render as "—" until the backend hydrates them from
        the resume submission. See PRODUCTION_TODO.md → P0 #4. */}
    <div className="a-card">
      <div className="a-card-header"><h3 className="a-card-title">個人資訊</h3></div>
      <div className="a-card-body">
        <dl className="a-dl">
          <dt>ID</dt><dd><Mono>{c.id}</Mono></dd>
          <dt>姓名</dt><dd>{c.name}</dd>
          <dt>年齡 / 性別</dt><dd>{c.age} 歲 · {c.gender==='F'?'女':'男'}</dd>
          <dt>國籍</dt><dd className="flex items-center gap-6"><Flag code={c.nationality} size={14}/> {ADMIN.nat[c.nationality].name}</dd>
          <dt>所在城市</dt><dd>{c.city}</dd>
          <dt>婚姻狀況</dt><dd>{c.maritalStatus || <span className="text-faint">—</span>}</dd>
          <dt>子女</dt><dd>{c.kids != null ? c.kids : <span className="text-faint">—</span>}</dd>
          <dt>宗教</dt><dd>{c.religion || <span className="text-faint">—</span>}</dd>
          <dt>教育</dt><dd>{c.education || <span className="text-faint">—</span>}</dd>
          <dt>身高 / 體重</dt><dd>{c.height && c.weight ? `${c.height} cm · ${c.weight} kg` : <span className="text-faint">—</span>}</dd>
          <dt>年資</dt><dd>{c.yearsExperience ? `${c.yearsExperience} 年` : <span className="text-faint">—</span>}</dd>
        </dl>
      </div>
    </div>

    {/* Contact — captured on signup + resume form. Email is verified at
        signup; phone / WhatsApp are self-reported in the resume form. */}
    <div className="a-card">
      <div className="a-card-header"><h3 className="a-card-title">聯絡資訊</h3></div>
      <div className="a-card-body">
        <dl className="a-dl">
          <dt>Email</dt><dd>{c.contactEmail || <span className="text-faint">—</span>}</dd>
          <dt>電話</dt><dd>{c.contactPhone ? `${c.contactPhoneCC || ''} ${c.contactPhone}`.trim() : <span className="text-faint">—</span>}</dd>
          <dt>WhatsApp</dt><dd>{c.whatsapp ? `${c.whatsappCC || ''} ${c.whatsapp}`.trim() : <span className="text-faint">—</span>}</dd>
        </dl>
      </div>
    </div>

    {/* Work preference — mirrors resume form's "Preference" step:
        position / job type / expected salary / day off / accommodation /
        contract status / available date / target markets. */}
    <div className="a-card">
      <div className="a-card-header"><h3 className="a-card-title">工作偏好</h3></div>
      <div className="a-card-body">
        <dl className="a-dl">
          <dt>職位類別</dt><dd>{c.position || <span className="text-faint">—</span>}</dd>
          <dt>工作型態</dt><dd className="flex gap-4 flex-wrap">{(c.jobTypes && c.jobTypes.length) ? c.jobTypes.map(j=>(<span key={j} className="a-badge a-badge-neutral">{j}</span>)) : <span className="text-faint">—</span>}</dd>
          <dt>預期薪資</dt><dd>{c.expectedSalary || <span className="text-faint">—</span>}</dd>
          <dt>休假</dt><dd>{c.dayOff || <span className="text-faint">—</span>}</dd>
          <dt>住宿</dt><dd>{c.accommodation || <span className="text-faint">—</span>}</dd>
          <dt>目標市場</dt><dd className="flex gap-4">{c.targetMarkets.map(m=>(<span key={m} className="a-badge a-badge-neutral">{m}</span>))}</dd>
          <dt>合約狀態</dt><dd>{c.contractStatus === 'FIRST_TIME' ? '首次出國' : c.contractStatus === 'TRANSFER' ? '轉換僱主' : '已完約'}</dd>
          <dt>可開始日期</dt><dd>{c.availableDate || <span className="text-faint">—</span>}</dd>
        </dl>
      </div>
    </div>

    {/* Account meta — backend-managed fields (not editable by helper). */}
    <div className="a-card">
      <div className="a-card-header"><h3 className="a-card-title">帳戶資訊</h3></div>
      <div className="a-card-body">
        <dl className="a-dl">
          <dt>完整度</dt><dd><div className="flex items-center gap-8"><div className="a-progress" style={{width:100,height:6}}><div className="a-progress-bar" style={{width:c.completeness+'%'}}/></div><span className="fs-12 fw-600 tnum">{c.completeness}%</span></div></dd>
          <dt>建立時間</dt><dd className="text-muted">{FMT.iso(c.createdAt)}</dd>
          <dt>{c.status==='PENDING_REVIEW'?'送審時間':'上架時間'}</dt><dd className="text-muted">{FMT.iso(c.submittedAt || c.reviewedAt)}</dd>
          <dt>最後活躍</dt><dd className="text-muted">{FMT.rel(c.lastActive)}</dd>
        </dl>
      </div>
    </div>
    <div className="a-card" style={{gridColumn:'1 / -1'}}>
      <div className="a-card-header"><h3 className="a-card-title">自我介紹（三語言）</h3></div>
      <div className="a-card-body">
        <div className="grid-3">
          <div><div className="fs-12 fw-600 mb-6 text-muted">🇹🇼 繁中</div><div className="fs-13" style={{whiteSpace:'pre-wrap',lineHeight:1.6}}>{c.bioZh || <span className="text-faint">（未填寫）</span>}</div></div>
          <div><div className="fs-12 fw-600 mb-6 text-muted">🇬🇧 English</div><div className="fs-13" style={{whiteSpace:'pre-wrap',lineHeight:1.6}}>{c.bioEn || <span className="text-faint">（未填寫）</span>}</div></div>
          <div><div className="fs-12 fw-600 mb-6 text-muted">🇮🇩 Indonesia</div><div className="fs-13" style={{whiteSpace:'pre-wrap',lineHeight:1.6}}>{c.bioId || <span className="text-faint">（未填寫）</span>}</div></div>
        </div>
      </div>
    </div>
    {(c.rejectReason || c.suspendReason) && (
      <div className="a-card" style={{gridColumn:'1 / -1', borderColor:'var(--a-danger-line)', background:'var(--a-danger-bg)'}}>
        <div className="a-card-body">
          <div className="fw-600 fs-13 mb-6 flex items-center gap-6" style={{color:'var(--a-danger)'}}>
            <AIcon name="alert" size={14}/> {c.rejectReason ? '駁回原因' : '停權原因'}
          </div>
          <div className="fs-13" style={{color:'var(--a-ink-soft)'}}>{c.rejectReason || c.suspendReason}</div>
        </div>
      </div>
    )}

    {/* Work history */}
    <div className="a-card" style={{gridColumn:'1 / -1'}}>
      <div className="a-card-header">
        <h3 className="a-card-title">工作經歷</h3>
        <span className="text-muted fs-12 ml-auto">{(c.workHistory || MOCK_WORK_HISTORY).length} 段經歷</span>
      </div>
      <div className="a-card-body">
        <table className="a-table a-table-compact">
          <thead><tr>
            <th style={{width:120}}>期間</th>
            <th>職務</th>
            <th>地點</th>
            <th>家庭結構</th>
            <th style={{width:140}}>離職原因</th>
            <th style={{width:100}}>Reference</th>
          </tr></thead>
          <tbody>
            {(c.workHistory || MOCK_WORK_HISTORY).map((w,i) => (
              <tr key={i}>
                <td className="fs-12 tnum">{w.from} – {w.to}<div className="text-muted fs-11">{w.years} 年</div></td>
                <td className="fw-500">{w.role}</td>
                <td><div className="flex items-center gap-6"><Flag code={w.flagCode} size={12}/> {w.city}</div></td>
                <td className="fs-12 text-muted">{w.household}</td>
                <td className="fs-12 text-muted">{w.endReason}</td>
                <td>{w.hasReference ? <span className="a-badge a-badge-success"><AIcon name="check" size={10}/> 有</span> : <span className="a-badge a-badge-neutral">無</span>}</td>
              </tr>
            ))}
          </tbody>
        </table>
      </div>
    </div>

    {/* Skills & Languages — pulls from the candidate's typedSkills (set
        by resume form). Falls back to a demo subset of SKILL_TAXONOMY when
        the field is missing on prototype mock candidates. Visual treatment
        mirrors Frontend/pages-main.jsx SkillsGroup so admin sees what
        employers see. */}
    {(() => {
      const tax = window.SKILL_TAXONOMY;
      const ts = c.typedSkills;
      const lookup = (cat, ids) => (tax && tax[cat]) ? tax[cat].filter(x => ids.includes(x.id)) : [];
      const demo = getDemoSkills();
      const groups = {
        languages:     ts && ts.languages ? lookup('languages', ts.languages) : demo.languages,
        mainSkills:    ts && ts.mainSkills ? lookup('mainSkills', ts.mainSkills) : demo.mainSkills,
        cookingSkills: ts && ts.cookingSkills ? lookup('cookingSkills', ts.cookingSkills) : demo.cookingSkills,
        otherSkills:   ts && ts.otherSkills ? lookup('otherSkills', ts.otherSkills) : demo.otherSkills,
        personality:   ts && ts.personality ? lookup('personality', ts.personality) : demo.personality,
      };
      return (
        <div className="a-card" style={{gridColumn:'1 / -1'}}>
          <div className="a-card-header">
            <h3 className="a-card-title">技能與語言</h3>
            <span className="text-muted fs-12 ml-auto">{ts ? '由候選人在履歷表單填寫' : '示範資料 — 候選人尚未填寫'}</span>
          </div>
          <div className="a-card-body">
            <div style={{display:'grid', gridTemplateColumns:'1fr 1fr', gap:'22px 28px'}}>
              <SkillBlock title="語言" category="language" required items={groups.languages}/>
              <SkillBlock title="主要技能" category="main" required items={groups.mainSkills}/>
              <SkillBlock title="烹飪技能" category="cooking" items={groups.cookingSkills}/>
              <SkillBlock title="其他技能" category="other" items={groups.otherSkills}/>
              <SkillBlock title="個性特質" category="personality" items={groups.personality} full/>
            </div>
          </div>
        </div>
      );
    })()}
  </div>
);

const CandAssessmentTab = ({ asm, c }) => {
  if (!asm) return <AEmpty icon="clipboardCheck" title="尚無評測" sub="此候選人尚未被評測。評測必須由評測員於泗水站建立。"/>;
  const avg = (asm.scores.reduce((s,x)=>s+x.score,0) / asm.scores.length).toFixed(1);
  return (
    <div className="a-card">
      <div className="a-card-header">
        <h3 className="a-card-title">評測 #<Mono>{asm.id}</Mono></h3>
        <div className="ml-auto flex items-center gap-8">
          <StatusBadge status={asm.status}/>
          {asm.tier && <Tier t={asm.tier}/>}
        </div>
      </div>
      <div className="a-card-body">
        <div className="grid-3 mb-16">
          <div><div className="text-muted fs-12">評測員</div><div className="fw-600 fs-13">{asm.assessor}</div></div>
          <div><div className="text-muted fs-12">站點</div><div className="fw-600 fs-13">{asm.station}</div></div>
          <div><div className="text-muted fs-12">平均分</div><div className="fw-600 fs-16">{avg} / 10</div></div>
        </div>
        <table className="a-table a-table-compact">
          <thead><tr><th>技能</th><th style={{width:100}}>分數</th><th>備註</th></tr></thead>
          <tbody>
            {asm.scores.map((s,i) => (
              <tr key={i}>
                <td className="fw-500" style={{color:'var(--a-ink)'}}>{s.skill}</td>
                <td><div className="flex items-center gap-6"><div className="a-progress" style={{width:60,height:5}}><div className="a-progress-bar" style={{width:(s.score*10)+'%', background: s.score >= 8 ? 'var(--a-success)' : s.score >= 6 ? 'var(--a-primary)' : 'var(--a-warn)'}}/></div><span className="fw-600 tnum fs-13">{s.score}</span></div></td>
                <td className="text-muted fs-12">{s.note || '—'}</td>
              </tr>
            ))}
          </tbody>
        </table>
        {asm.overallNote && <div className="mt-14 p-12" style={{padding:12,background:'var(--a-paper-2)',borderRadius:6,borderLeft:'3px solid var(--a-primary)'}}><div className="fs-12 fw-600 text-muted mb-4">總評</div><div className="fs-13">{asm.overallNote}</div></div>}
        {asm.signedAt && <div className="mt-14 text-muted fs-12">✓ 由 {asm.signedBy} 於 {FMT.iso(asm.signedAt)} 簽核</div>}
      </div>
    </div>
  );
};

const CandVideosTab = ({ videos }) => {
  const toast = useToast();
  const [openId, setOpenId] = uS(null);
  const [localVids, setLocalVids] = uS(() => videos.map(v => ({ ...v })));

  uE(() => { setLocalVids(videos.map(v => ({...v}))); }, [videos]);

  const openV = localVids.find(v => v.id === openId);
  if (localVids.length === 0) return <AEmpty icon="video" title="尚無影片" sub="候選人可自拍 Intro 影片、或由評測員上傳 PLATFORM 影片。"/>;

  const actorName = () => (window.__currentAdminName || 'System');
  const patch = (id, diff, logAction, logDiff) => {
    setLocalVids(xs => xs.map(v => v.id === id ? { ...v, ...diff } : v));
    const i = ADMIN.videos.findIndex(v => v.id === id);
    if (i >= 0) ADMIN.videos[i] = { ...ADMIN.videos[i], ...diff };
    if (logAction) ADMIN.auditLogs.unshift({ id:'log_'+Math.random().toString(36).slice(2,8), at:new Date(), actor: actorName(), action: logAction, targetType:'Video', targetId: id, diff: logDiff || {} });
  };
  const approve = (id) => { patch(id, { moderationStatus:'APPROVED', moderatedBy: actorName(), moderatedAt:new Date(), moderationNote:null }, 'VIDEO_APPROVE', { moderationStatus:{before:'PENDING',after:'APPROVED'} }); toast('已核准', 'success'); };
  const reject  = (id, r) => { patch(id, { moderationStatus:'REJECTED', moderatedBy: actorName(), moderatedAt:new Date(), moderationNote:r }, 'VIDEO_REJECT', { moderationStatus:{before:'PENDING',after:'REJECTED'}, reason:r }); toast('已駁回', 'warn'); };
  const remove  = (id) => {
    setLocalVids(xs => xs.filter(v => v.id !== id));
    const i = ADMIN.videos.findIndex(v => v.id === id);
    if (i >= 0) ADMIN.videos.splice(i, 1);
    ADMIN.auditLogs.unshift({ id:'log_'+Math.random().toString(36).slice(2,8), at:new Date(), actor: actorName(), action:'VIDEO_DELETE', targetType:'Video', targetId: id, diff:{} });
    toast('已刪除', 'danger');
  };
  const restore = (id) => { patch(id, { moderationStatus:'PENDING', moderatedBy:null, moderatedAt:null, moderationNote:null }, 'VIDEO_RESTORE', { moderationStatus:{before:'REJECTED',after:'PENDING'} }); toast('已復原為待審', 'info'); };

  const platformVids = localVids.filter(v => v.source === 'PLATFORM');
  const selfVids     = localVids.filter(v => v.source === 'SELF');

  const renderGroup = (label, list) => list.length === 0 ? null : (
    <div className="mb-16">
      <div className="flex items-baseline gap-8 mb-10" style={{paddingBottom:8,borderBottom:'1px solid var(--a-line)'}}>
        <span className="fs-12 fw-700" style={{letterSpacing:'0.04em',textTransform:'uppercase'}}>{label}</span>
        <span className="fs-11 text-muted">{list.length} 支</span>
      </div>
      <div style={{display:'grid',gridTemplateColumns:'repeat(auto-fill,minmax(230px,1fr))',gap:14}}>
        {list.map(v => {
          const cand = ADMIN.candidates.find(c => c.id === v.candidateId);
          return (
            <VideoCard key={v.id} v={v} cand={cand}
              selectable={false}
              onClick={()=>setOpenId(v.id)}
              onApprove={()=>approve(v.id)}
              onReject={(r)=>reject(v.id, r)}
              onDelete={()=>remove(v.id)}
              onRestore={()=>restore(v.id)}
            />
          );
        })}
      </div>
    </div>
  );

  return (
    <div>
      {renderGroup('PLATFORM 影片（評測員上傳）', platformVids)}
      {renderGroup('SELF 影片（候選人自拍）', selfVids)}
      {openV && <VideoDrawer v={openV} onClose={()=>setOpenId(null)} onApprove={approve} onReject={reject} onDelete={(id)=>{ remove(id); setOpenId(null); }} onRestore={restore}/>}
    </div>
  );
};

const CandReferralsTab = ({ asReferrer, asReferee }) => {
  // Bonus payouts where this candidate is the referrer (receives the bonus)
  const referrerIds = new Set(asReferrer.map(r => r.id));
  const payouts = (ADMIN.payouts || []).filter(p => referrerIds.has(p.referralId));
  const totalPaidUsd = payouts.filter(p => p.status === 'PAID').reduce((s,p) => s + p.amountUsdCents, 0);
  const pendingPayable = payouts.filter(p => p.status === 'PAYABLE');
  const onHold = payouts.filter(p => p.status === 'HOLD');

  return (
    <div className="fcol gap-16">
      {/* Bonus summary — only when there are any referrals */}
      {asReferrer.length > 0 && (
        <div className="a-card">
          <div className="a-card-header">
            <h3 className="a-card-title"><AIcon name="wallet" size={14}/> Referral 獎金紀錄</h3>
            <span className="text-muted fs-12 ml-auto">僅限超級管理員可執行發放</span>
          </div>
          <div className="a-card-body">
            <div className="grid-3 mb-14">
              <div style={{padding:12,border:'1px solid var(--a-success-line)',background:'var(--a-success-bg)',borderRadius:8}}>
                <div className="fs-11 fw-600" style={{color:'var(--a-success)',letterSpacing:'0.05em',textTransform:'uppercase'}}>累計已發放</div>
                <div style={{fontSize:22,fontWeight:700,color:'var(--a-success)',letterSpacing:'-0.02em',marginTop:4}}>{FMT.usd(totalPaidUsd)}</div>
                <div className="fs-11 text-muted mt-2">{payouts.filter(p=>p.status==='PAID').length} 筆</div>
              </div>
              <div style={{padding:12,border:'1px solid var(--a-warn-line)',background:'var(--a-warn-bg)',borderRadius:8}}>
                <div className="fs-11 fw-600" style={{color:'var(--a-warn-text)',letterSpacing:'0.05em',textTransform:'uppercase'}}>可發放</div>
                <div style={{fontSize:22,fontWeight:700,color:'var(--a-warn-text)',letterSpacing:'-0.02em',marginTop:4}}>{FMT.usd(pendingPayable.reduce((s,p)=>s+p.amountUsdCents,0))}</div>
                <div className="fs-11 text-muted mt-2">{pendingPayable.length} 筆待匯款</div>
              </div>
              <div style={{padding:12,border:'1px solid var(--a-line)',background:'var(--a-paper-2)',borderRadius:8}}>
                <div className="fs-11 fw-600 text-muted" style={{letterSpacing:'0.05em',textTransform:'uppercase'}}>觀察中 HOLD</div>
                <div style={{fontSize:22,fontWeight:700,letterSpacing:'-0.02em',marginTop:4}}>{FMT.usd(onHold.reduce((s,p)=>s+p.amountUsdCents,0))}</div>
                <div className="fs-11 text-muted mt-2">{onHold.length} 筆 14 天觀察</div>
              </div>
            </div>
            {payouts.length === 0 ? (
              <div style={{padding:'14px 16px',background:'var(--a-paper-2)',borderRadius:6,fontSize:13,color:'var(--a-ink-soft)'}}>
                目前尚無任何獎金紀錄。被推薦人通過 4 步驟（註冊 → 身份驗證 → SELF → 合格）後，將建立獎金記錄。
              </div>
            ) : (
              <table className="a-table a-table-compact" style={{marginTop:4}}>
                <thead><tr>
                  <th style={{width:110}}>Payout ID</th>
                  <th>對應 Referral</th>
                  <th style={{width:100}}>USD</th>
                  <th style={{width:150}}>本地（參考）</th>
                  <th style={{width:110}}>狀態</th>
                  <th style={{width:150}}>發放時間</th>
                  <th>外部交易 ID</th>
                </tr></thead>
                <tbody>
                  {payouts.map(p => {
                    const r = asReferrer.find(x => x.id === p.referralId);
                    const referee = r && ADMIN.candidates.find(c => c.id === r.refereeId);
                    return (
                      <tr key={p.id}>
                        <td><Mono>{p.id}</Mono></td>
                        <td>{referee ? <span className="fs-13">推薦 <b>{referee.name}</b></span> : <Mono>{p.referralId}</Mono>}</td>
                        <td className="fw-700 fs-13">{FMT.usd(p.amountUsdCents)}</td>
                        <td className="text-muted fs-12">{FMT.local(p.amountLocalCents, p.localCurrency)}</td>
                        <td><BonusStatus status={p.status}/></td>
                        <td className="text-muted fs-12">{p.paidAt ? FMT.iso(p.paidAt) : '—'}</td>
                        <td className="fs-12">{p.externalTxId ? <Mono>{p.externalTxId}</Mono> : <span className="text-faint">—</span>}</td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            )}
          </div>
        </div>
      )}

      <div className="a-card">
        <div className="a-card-header">
          <h3 className="a-card-title">作為 Referrer</h3>
          <span className="text-muted fs-12 ml-auto">{asReferrer.length} 筆推薦</span>
        </div>
        <div className="a-card-body a-card-body-flush">
          {asReferrer.length === 0 ? <AEmpty title="尚未推薦過他人" sub="分享 Referral 連結邀請朋友加入。"/> : (
            <table className="a-table a-table-compact">
              <thead><tr><th>被推薦人</th><th>狀態</th><th>推薦時間</th><th>推薦碼</th></tr></thead>
              <tbody>
                {asReferrer.map(r => {
                  const referee = ADMIN.candidates.find(c => c.id === r.refereeId);
                  return (<tr key={r.id}>
                    <td>{referee ? <CandidateCell c={referee}/> : <Mono>{r.refereeId}</Mono>}</td>
                    <td><StatusBadge status={r.status}/></td>
                    <td className="text-muted">{FMT.rel(r.refereeSignupAt)}</td>
                    <td><Mono>{r.code}</Mono></td>
                  </tr>);
                })}
              </tbody>
            </table>
          )}
        </div>
      </div>
      <div className="a-card">
        <div className="a-card-header">
          <h3 className="a-card-title">被誰推薦</h3>
        </div>
        <div className="a-card-body a-card-body-flush">
          {asReferee.length === 0 ? <AEmpty title="直接註冊，未經他人推薦"/> : (
            <table className="a-table a-table-compact">
              <thead><tr><th>推薦人</th><th>狀態</th><th>推薦碼</th></tr></thead>
              <tbody>
                {asReferee.map(r => {
                  const referrer = ADMIN.candidates.find(c => c.id === r.referrerId);
                  return (<tr key={r.id}>
                    <td>{referrer ? <CandidateCell c={referrer}/> : <Mono>{r.referrerId}</Mono>}</td>
                    <td><StatusBadge status={r.status}/></td>
                    <td><Mono>{r.code}</Mono></td>
                  </tr>);
                })}
              </tbody>
            </table>
          )}
        </div>
      </div>
    </div>
  );
};

// Action codes → human-readable Chinese labels for the history timeline.
const CAND_ACTION_LABEL = {
  CANDIDATE_APPROVE:         '通過審核並上架',
  CANDIDATE_REJECT:          '駁回送審',
  CANDIDATE_REQUEST_CHANGES: '要求補件',
  CANDIDATE_SUSPEND:         '停權',
  CANDIDATE_UNSUSPEND:       '恢復上架',
  CANDIDATE_RESUME_EDIT:     '編輯履歷資料',
  VIDEO_APPROVE:             '核准影片',
  VIDEO_REJECT:              '駁回影片',
  VIDEO_DELETE:              '刪除影片',
  VIDEO_RESTORE:             '復原影片為待審',
  VIDEO_UPLOAD:              '上傳 PLATFORM 影片',
  ASSESSMENT_CREATE:         '建立評測',
  ASSESSMENT_SIGN:           '簽核評測',
};

// Render a candidate audit-log diff into a short, human-friendly summary.
// Skips noisy keys like `reason` (already rendered as a separate paragraph).
const renderDiffSummary = (diff) => {
  if (!diff || typeof diff !== 'object') return null;
  const entries = Object.entries(diff).filter(([k]) => k !== 'reason');
  if (entries.length === 0) return null;
  return entries.map(([k, v]) => {
    if (v && typeof v === 'object' && 'before' in v && 'after' in v) {
      return `${k}：${JSON.stringify(v.before)} → ${JSON.stringify(v.after)}`;
    }
    return `${k}：${typeof v === 'string' ? v : JSON.stringify(v)}`;
  }).join('；');
};

const CandHistoryTab = ({ c }) => {
  const logs = ADMIN.auditLogs.filter(l => l.targetId === c.id);
  const timeline = [
    { at: c.createdAt, kind:'info',    icon:'plus',        title:'帳號建立', actor:'候選人本人',                          detail:'候選人在平台建立帳號' },
    c.submittedAt && { at: c.submittedAt, kind:'warn',    icon:'upload',      title:c.resubmit?'重新送審':'首次送審', actor:'候選人本人', detail:`完整度 ${c.completeness}%，目標市場 ${c.targetMarkets.join(', ')}` },
    c.reviewedAt  && { at: c.reviewedAt,  kind:'success', icon:'checkCircle', title:'審核通過並上架',                actor: c.reviewedBy || '系統',  detail:`Tier ${c.tier || '—'}` },
    ...logs.map(l => ({
      at: l.at,
      kind: /APPROVE|UNSUSPEND|RESTORE|SIGN/.test(l.action) ? 'success'
          : /REJECT|SUSPEND|DELETE/.test(l.action)         ? 'warn'
          : 'info',
      icon: l.action.includes('RESUME_EDIT') ? 'edit' : l.action.includes('VIDEO') ? 'video' : 'history',
      title: CAND_ACTION_LABEL[l.action] || l.action.replace(/_/g,' '),
      actor: l.actor || '系統',
      detail: renderDiffSummary(l.diff),
      reason: l.diff?.reason || null,
    })),
  ].filter(Boolean).sort((a,b) => new Date(b.at) - new Date(a.at));

  return (
    <div style={{position:'relative',paddingLeft:8}}>
      {timeline.map((e,i) => (
        <div key={i} style={{display:'flex',gap:14,paddingBottom:18,position:'relative'}}>
          <div style={{position:'relative',zIndex:1}}>
            <div style={{width:28,height:28,borderRadius:'50%',display:'grid',placeItems:'center',
              background: e.kind==='success'?'var(--a-success-bg)':e.kind==='warn'?'var(--a-warn-bg)':'var(--a-info-bg)',
              color: e.kind==='success'?'var(--a-success)':e.kind==='warn'?'var(--a-warn)':'var(--a-info)',
              border: '2px solid ' + (e.kind==='success'?'var(--a-success-line)':e.kind==='warn'?'var(--a-warn-line)':'var(--a-info-line)')}}>
              <AIcon name={e.icon} size={13}/>
            </div>
            {i < timeline.length-1 && <div style={{position:'absolute',left:13,top:28,width:2,bottom:-18,background:'var(--a-line)'}}/>}
          </div>
          <div style={{flex:1,minWidth:0}}>
            <div style={{display:'flex',alignItems:'center',gap:8,flexWrap:'wrap'}}>
              <div className="fw-600 fs-13">{e.title}</div>
              {e.actor && <span className="a-badge a-badge-neutral" style={{fontSize:10,padding:'1px 7px'}}>由 {e.actor}</span>}
            </div>
            <div className="text-muted fs-12 mt-2">{FMT.iso(e.at)} · {FMT.rel(e.at)}</div>
            {e.detail && <div className="fs-13 mt-6" style={{color:'var(--a-ink-soft)',wordBreak:'break-word'}}>{e.detail}</div>}
            {e.reason && <div className="fs-13 mt-4" style={{color:'var(--a-ink-soft)',padding:'6px 10px',background:'var(--a-paper-2)',borderLeft:'2px solid var(--a-line)',borderRadius:4}}>原因：{e.reason}</div>}
          </div>
        </div>
      ))}
    </div>
  );
};

// ========= Documents tab =========
const DOC_STATUS_LABEL = { VERIFIED:'已核實', PENDING:'待審核', REJECTED:'已駁回' };
const DOC_STATUS_CLASS = { VERIFIED:'a-badge-success', PENDING:'a-badge-warn', REJECTED:'a-badge-danger' };

const docIconFor = (ext) => {
  const e = (ext || '').toLowerCase();
  if (['jpg','jpeg','png','heic','webp'].includes(e)) return 'image';
  if (e === 'pdf') return 'file';
  return 'paperclip';
};
const fmtSize = (kb) => kb >= 1024 ? (kb/1024).toFixed(1) + ' MB' : kb + ' KB';
const expiryInfo = (iso) => {
  if (!iso) return null;
  const days = Math.round((new Date(iso) - new Date()) / 86400000);
  if (days < 0)      return { text: `已過期 ${Math.abs(days)} 天`, tone:'danger' };
  if (days < 60)     return { text: `${days} 天內到期`,             tone:'warn' };
  if (days < 180)    return { text: `${days} 天後到期`,             tone:'warn' };
  return { text: `有效（${days} 天後到期）`, tone:'muted' };
};

const CandDocumentsTab = ({ c }) => {
  const docs = c.documents || MOCK_DOCUMENTS;
  const [catFilter, setCatFilter] = uS('');
  const [statusFilter, setStatusFilter] = uS('');
  const [previewDoc, setPreviewDoc] = uS(null);

  const filtered = docs.filter(d =>
    (!catFilter || d.category === catFilter) &&
    (!statusFilter || d.status === statusFilter)
  );

  const byCategory = DOC_CATEGORIES.map(cat => ({
    ...cat,
    items: filtered.filter(d => d.category === cat.key),
    total: docs.filter(d => d.category === cat.key).length,
  }));

  const verifiedCount = docs.filter(d => d.status === 'VERIFIED').length;
  const pendingCount  = docs.filter(d => d.status === 'PENDING').length;
  const rejectedCount = docs.filter(d => d.status === 'REJECTED').length;
  const expiringSoon  = docs.filter(d => d.expiresAt && (new Date(d.expiresAt) - new Date())/86400000 < 180 && (new Date(d.expiresAt) - new Date()) > 0).length;

  return (
    <div>
      {/* Summary strip */}
      <div className="grid-4 mb-16">
        <div className="a-card"><div className="a-card-body" style={{padding:14}}>
          <div className="text-muted fs-12 mb-4">總文件數</div>
          <div style={{fontSize:26,fontWeight:700,lineHeight:1,fontVariantNumeric:'tabular-nums'}}>{docs.length}</div>
        </div></div>
        <div className="a-card"><div className="a-card-body" style={{padding:14}}>
          <div className="text-muted fs-12 mb-4">已核實</div>
          <div style={{fontSize:26,fontWeight:700,lineHeight:1,color:'var(--a-success)',fontVariantNumeric:'tabular-nums'}}>{verifiedCount}</div>
        </div></div>
        <div className="a-card"><div className="a-card-body" style={{padding:14}}>
          <div className="text-muted fs-12 mb-4">待審核 / 駁回</div>
          <div style={{fontSize:26,fontWeight:700,lineHeight:1,color:'var(--a-warn)',fontVariantNumeric:'tabular-nums'}}>{pendingCount}<span style={{fontSize:14,color:'var(--a-danger)',marginLeft:6}}>/ {rejectedCount}</span></div>
        </div></div>
        <div className="a-card"><div className="a-card-body" style={{padding:14}}>
          <div className="text-muted fs-12 mb-4">半年內到期</div>
          <div style={{fontSize:26,fontWeight:700,lineHeight:1,color: expiringSoon > 0 ? 'var(--a-warn)' : 'var(--a-ink)',fontVariantNumeric:'tabular-nums'}}>{expiringSoon}</div>
        </div></div>
      </div>

      {/* Toolbar */}
      <div className="a-card mb-16">
        <div className="a-table-toolbar">
          <select className="a-select" style={{width:160}} value={catFilter} onChange={e=>setCatFilter(e.target.value)}>
            <option value="">所有類別</option>
            {DOC_CATEGORIES.map(x => <option key={x.key} value={x.key}>{x.label}</option>)}
          </select>
          <select className="a-select" style={{width:130}} value={statusFilter} onChange={e=>setStatusFilter(e.target.value)}>
            <option value="">所有狀態</option>
            <option value="VERIFIED">已核實</option>
            <option value="PENDING">待審核</option>
            <option value="REJECTED">已駁回</option>
          </select>
          <span className="text-muted fs-13">共 {filtered.length} / {docs.length} 筆</span>
          <div className="ml-auto flex gap-6">
            <button className="a-btn a-btn-sm a-btn-default"><AIcon name="download" size={12}/> 下載全部 (ZIP)</button>
            <button className="a-btn a-btn-sm a-btn-primary"><AIcon name="upload" size={12}/> 代上傳文件</button>
          </div>
        </div>
      </div>

      {/* Categories */}
      <div className="fcol gap-14">
        {byCategory.map(cat => {
          if (cat.items.length === 0 && (catFilter || statusFilter)) return null;
          return (
            <div key={cat.key} className="a-card">
              <div className="a-card-header">
                <h3 className="a-card-title flex items-center gap-8">
                  <AIcon name={cat.icon} size={15}/>
                  {cat.label}
                  {cat.required && <span className="a-badge a-badge-neutral" style={{padding:'1px 7px',fontSize:10}}>必備</span>}
                </h3>
                <span className="text-muted fs-12 ml-auto">{cat.items.length} / {cat.total} 份</span>
              </div>
              <div className="a-card-body a-card-body-flush">
                {cat.items.length === 0 ? (
                  <div style={{padding:'20px 16px',textAlign:'center'}} className="fs-13 text-muted">
                    {cat.required ? <span style={{color:'var(--a-warn)'}}><AIcon name="alert" size={12}/> 此類別為必備，尚未上傳</span> : <span className="text-faint">尚未上傳此類文件</span>}
                  </div>
                ) : (
                  <table className="a-table a-table-compact">
                    <thead><tr>
                      <th>文件</th>
                      <th style={{width:160}}>類型 / 檔案</th>
                      <th style={{width:140}}>發證 / 到期</th>
                      <th style={{width:120}}>上傳時間</th>
                      <th style={{width:100}}>狀態</th>
                      <th style={{width:80}}>操作</th>
                    </tr></thead>
                    <tbody>
                      {cat.items.map(d => {
                        const exp = expiryInfo(d.expiresAt);
                        return (
                          <tr key={d.id} className="clickable" onClick={()=>setPreviewDoc(d)}>
                            <td>
                              <div className="flex items-center gap-10">
                                <div style={{width:34,height:34,background:'var(--a-paper-2)',border:'1px solid var(--a-line)',borderRadius:5,display:'grid',placeItems:'center',color:'var(--a-ink-soft)',flexShrink:0}}>
                                  <AIcon name={docIconFor(d.ext)} size={16}/>
                                </div>
                                <div style={{minWidth:0}}>
                                  <div className="fw-600 fs-13" style={{color:'var(--a-ink)',overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{d.type}</div>
                                  <div className="text-muted fs-11" style={{overflow:'hidden',textOverflow:'ellipsis',whiteSpace:'nowrap'}}>{d.name}</div>
                                </div>
                              </div>
                            </td>
                            <td>
                              <div className="fs-12"><span className="a-badge a-badge-neutral" style={{textTransform:'uppercase',padding:'1px 5px',fontSize:10}}>{d.ext}</span> <span className="text-muted tnum">{fmtSize(d.sizeKb)}</span></div>
                              {d.pages > 1 && <div className="text-muted fs-11">{d.pages} 頁</div>}
                            </td>
                            <td className="fs-12">
                              {d.issuedAt && <div><span className="text-muted">發：</span><span className="tnum">{d.issuedAt}</span></div>}
                              {d.expiresAt && (
                                <div>
                                  <span className="text-muted">到：</span><span className="tnum">{d.expiresAt}</span>
                                  {exp && <div className={`fs-11 ${exp.tone==='danger'?'text-danger':exp.tone==='warn'?'text-warn':'text-muted'}`} style={exp.tone==='danger'?{color:'var(--a-danger)'}:exp.tone==='warn'?{color:'var(--a-warn)'}:undefined}>{exp.text}</div>}
                                </div>
                              )}
                              {!d.issuedAt && !d.expiresAt && <span className="text-faint">—</span>}
                            </td>
                            <td className="text-muted fs-12">{FMT.rel(d.uploadedAt)}</td>
                            <td><span className={`a-badge ${DOC_STATUS_CLASS[d.status]}`}>{DOC_STATUS_LABEL[d.status]}</span></td>
                            <td onClick={e=>e.stopPropagation()}>
                              <div className="flex gap-4">
                                <button className="a-icon-btn" title="預覽" onClick={()=>setPreviewDoc(d)}><AIcon name="eye" size={13}/></button>
                                <button className="a-icon-btn" title="下載"><AIcon name="download" size={13}/></button>
                              </div>
                            </td>
                          </tr>
                        );
                      })}
                    </tbody>
                  </table>
                )}
              </div>
            </div>
          );
        })}
      </div>

      {previewDoc && <DocPreviewModal doc={previewDoc} onClose={()=>setPreviewDoc(null)}/>}
    </div>
  );
};

const DocPreviewModal = ({ doc, onClose }) => {
  const toast = useToast();
  const exp = expiryInfo(doc.expiresAt);
  return (
    <AModal open onClose={onClose} size="lg" title={<span className="flex items-center gap-8"><AIcon name={docIconFor(doc.ext)} size={16}/> {doc.type}</span>}
      footer={<>
        {doc.status === 'PENDING' && <>
          <button className="a-btn a-btn-danger" onClick={()=>{ toast('已駁回此文件', 'danger'); onClose(); }}><AIcon name="xCircle" size={13}/> 駁回</button>
          <button className="a-btn a-btn-success-solid" onClick={()=>{ toast('已標記為已核實', 'success'); onClose(); }}><AIcon name="check" size={13}/> 核實通過</button>
        </>}
        <div style={{flex:1}}/>
        <button className="a-btn a-btn-default"><AIcon name="download" size={13}/> 下載</button>
        <button className="a-btn a-btn-ghost" onClick={onClose}>關閉</button>
      </>}>
      {/* Preview canvas — placeholder document */}
      <div style={{aspectRatio:'4/5',maxHeight:480,background:'linear-gradient(135deg,#2A3548,#1A2234)',borderRadius:6,display:'grid',placeItems:'center',position:'relative',marginBottom:16,overflow:'hidden'}}>
        <div style={{position:'absolute',inset:0,opacity:0.08,backgroundImage:'repeating-linear-gradient(45deg,transparent 0 14px,#fff 14px 15px)'}}/>
        <div style={{textAlign:'center',color:'rgba(255,255,255,0.85)',padding:20}}>
          <AIcon name={docIconFor(doc.ext)} size={48}/>
          <div className="fs-13 mt-10 fw-600">{doc.name}</div>
          <div className="fs-11" style={{opacity:0.7,marginTop:4}}>{doc.ext.toUpperCase()} · {fmtSize(doc.sizeKb)} {doc.pages > 1 && ` · ${doc.pages} 頁`}</div>
          <div className="fs-11" style={{opacity:0.5,marginTop:10}}>（示意圖 — 實際部署接真實文件檢視器）</div>
        </div>
        <span className={`a-badge ${DOC_STATUS_CLASS[doc.status]}`} style={{position:'absolute',top:12,right:12}}>{DOC_STATUS_LABEL[doc.status]}</span>
      </div>

      <dl className="a-dl">
        <dt>文件 ID</dt><dd><Mono>{doc.id}</Mono></dd>
        <dt>類型</dt><dd>{doc.type}</dd>
        <dt>檔名</dt><dd>{doc.name}</dd>
        <dt>格式 / 大小</dt><dd>{doc.ext.toUpperCase()} · {fmtSize(doc.sizeKb)}{doc.pages > 1 && ` · ${doc.pages} 頁`}</dd>
        {doc.issuedAt && <><dt>發證日期</dt><dd className="tnum">{doc.issuedAt}</dd></>}
        {doc.expiresAt && <><dt>到期日期</dt><dd><span className="tnum">{doc.expiresAt}</span>{exp && <span className="fs-11" style={{marginLeft:8,color: exp.tone==='danger'?'var(--a-danger)':exp.tone==='warn'?'var(--a-warn)':'var(--a-ink-soft)'}}>· {exp.text}</span>}</dd></>}
        <dt>上傳時間</dt><dd>{FMT.iso(doc.uploadedAt)}</dd>
        {doc.verifiedBy && <><dt>核實者</dt><dd>{doc.verifiedBy} <span className="text-muted fs-11">· {FMT.rel(doc.verifiedAt)}</span></dd></>}
        {doc.note && <><dt>備註</dt><dd>{doc.note}</dd></>}
        {doc.rejectReason && <><dt style={{color:'var(--a-danger)'}}>駁回原因</dt><dd style={{color:'var(--a-danger)'}}>{doc.rejectReason}</dd></>}
      </dl>
    </AModal>
  );
};

Object.assign(window, { CandidatesPageReal });
