// HelperMatch Admin — Phase B6a: Referrals
// List (6 tabs by status) + detail drawer (gates timeline, risk signals, void/approve)

// ============================================================
// Shared bits — status + attribution + currency
// ============================================================
const REF_STATUS_MAP = {
  PENDING:   { label: '進行中',   kind: 'neutral',  desc: '已註冊，尚未通過審核' },
  QUALIFIED: { label: '已通過',   kind: 'info',     desc: '審核通過，進入 14 天 HOLD' },  // legacy alias for HOLD; safe to drop after backfill
  HOLD:      { label: 'HOLD',     kind: 'warn',     desc: '觀察期，14 天後轉為 PAYABLE' },
  PAYABLE:   { label: '可發放',   kind: 'primary',  desc: 'HOLD 結束，可加入下次批次' },
  PAID:      { label: '已發放',   kind: 'success',  desc: '已轉帳完成' },
  VOID:      { label: '已作廢',   kind: 'danger',   desc: '風控判定，不發放' },
};

const RefStatus = ({ status }) => {
  const m = REF_STATUS_MAP[status] || { label: status, kind: 'neutral' };
  return <span className={`a-badge a-badge-${m.kind}`}>{m.label}</span>;
};

const ATTR_SOURCE_MAP = {
  cookie:       { label: 'Cookie',   icon: 'globe' },
  manual_input: { label: '手動輸入', icon: 'edit' },
  qr:           { label: 'QR Code',  icon: 'external' },
};
const AttrPill = ({ source }) => {
  const m = ATTR_SOURCE_MAP[source] || { label: source, icon: 'link' };
  return (
    <span style={{
      display:'inline-flex',alignItems:'center',gap:4,
      padding:'2px 7px',fontSize:11,borderRadius:4,fontWeight:500,
      background:'var(--a-paper-2)',color:'var(--a-ink-soft)',border:'1px solid var(--a-line)',
    }}>
      <AIcon name={m.icon} size={10}/>{m.label}
    </span>
  );
};

// Void categories used in void modal + logs
const VOID_CATEGORIES = [
  { key: 'SELF_REF',       label: '自我推薦',   desc: '裝置指紋、IP、手機相近，明顯刷量' },
  { key: 'SYBIL',          label: '洗帳號',     desc: '被介紹人 SUSPENDED / 被雇主投訴' },
  { key: 'INVALID_SIGNUP', label: '無效註冊',   desc: '被介紹人註銷 / 重複帳號' },
  { key: 'VIOLATION',      label: '違反條款',   desc: '其他風控規則觸發' },
  { key: 'MANUAL',         label: '手動作廢',   desc: '人工審查後判定' },
];

// ============================================================
// REFERRALS PAGE
// ============================================================
const ReferralsPageReal = () => {
  const { t } = (window.useI18n ? window.useI18n() : { t: (k)=>k });
  const toast = useToast();
  const { navigate } = useRoute();
  const [tab, setTab] = uS(() => {
    const m = window.location.hash.match(/status=([A-Z]+)/);
    return m ? m[1] : 'ALL';
  });
  const [q, setQ] = uS('');
  const [natFilter, setNatFilter] = uS('');
  const [riskFilter, setRiskFilter] = uS('');
  const [sortBy, setSortBy] = uS('newest');
  const [openId, setOpenId] = uS(null);
  const [refs, setRefs] = uS(() => ADMIN.referrals.map(r => ({ ...r })));

  const tabs = [
    { key: 'ALL',       label: '全部' },
    { key: 'PENDING',   label: '待達成' },
    { key: 'HOLD',      label: 'HOLD 觀察中' },
    { key: 'PAYABLE',   label: '可發放' },
    { key: 'PAID',      label: '已發放' },
    { key: 'VOID',      label: '已作廢' },
  ];
  const tabCount = (k) => k === 'ALL' ? refs.length : refs.filter(r => r.status === k).length;

  const riskyCount = uM(() => refs.filter(r => r.suspicious).length, [refs]);

  const filtered = uM(() => {
    let list = refs;
    if (tab !== 'ALL') list = list.filter(r => r.status === tab);
    if (natFilter) {
      list = list.filter(r => {
        const referrer = ADMIN.candidates.find(c => c.id === r.referrerId);
        return referrer && referrer.nationality === natFilter;
      });
    }
    if (riskFilter === 'suspicious') list = list.filter(r => r.suspicious);
    if (q) {
      const qL = q.toLowerCase();
      list = list.filter(r => {
        const er = ADMIN.candidates.find(c => c.id === r.referrerId);
        const ee = ADMIN.candidates.find(c => c.id === r.refereeId);
        return r.id.toLowerCase().includes(qL)
          || (r.code || '').toLowerCase().includes(qL)
          || (er && (er.name.toLowerCase().includes(qL) || er.id.toLowerCase().includes(qL)))
          || (ee && (ee.name.toLowerCase().includes(qL) || ee.id.toLowerCase().includes(qL)));
      });
    }
    list = [...list];
    if (sortBy === 'newest') list.sort((a,b) => new Date(b.refereeSignupAt) - new Date(a.refereeSignupAt));
    if (sortBy === 'oldest') list.sort((a,b) => new Date(a.refereeSignupAt) - new Date(b.refereeSignupAt));
    if (sortBy === 'risk')   list.sort((a,b) => (b.suspicious?1:0) - (a.suspicious?1:0));
    return list;
  }, [refs, tab, q, natFilter, riskFilter, sortBy]);

  // KPIs
  const kpi = uM(() => {
    const pending = refs.filter(r => r.status === 'PENDING').length;
    const hold    = refs.filter(r => r.status === 'HOLD').length;
    const payable = refs.filter(r => r.status === 'PAYABLE').length;
    const paid30d = refs.filter(r => r.status === 'PAID' && r.paidAt && (ADMIN.now - r.paidAt) < 30*86400000).length;
    // Total PAID bonus 30d in USD — single $5 per successful referral
    // (milestone bonus removed; only the BASE bonus exists now).
    let paid30dUsd = 0;
    refs.forEach(r => {
      if (r.status === 'PAID' && r.paidAt && (ADMIN.now - r.paidAt) < 30*86400000) {
        paid30dUsd += 500;
      }
    });
    return { pending, hold, payable, paid30d, paid30dUsd, risky: riskyCount };
  }, [refs, riskyCount]);

  const openRef = refs.find(r => r.id === openId);

  const update = (id, patch, logAction, logDiff) => {
    setRefs(xs => xs.map(r => r.id === id ? { ...r, ...patch } : r));
    if (logAction) pushLog(logAction, 'Referral', id, logDiff);
  };

  return (
    <div className="a-page a-page-wide">
      <div className="a-page-header">
        <div>
          <h1 className="a-page-title">{t('admin.referrals.title')}</h1>
          <p className="a-page-sub">{t('admin.referrals.subtitle')}</p>
        </div>
        <div className="a-page-header-actions">
          {isSuperAdmin(useAuth().user) && (
            <button className="a-btn a-btn-default" onClick={()=>navigate('/payouts')}>
              <AIcon name="wallet" size={13}/> 獎金發放記錄
            </button>
          )}
        </div>
      </div>

      {/* KPI row */}
      <div className="grid-5 mb-20">
        <KpiCard icon="clock"  tone="neutral" label="待達成"     value={kpi.pending}/>
        <KpiCard icon="eye"    tone="warn"    label="HOLD 觀察中" value={kpi.hold}/>
        <KpiCard icon="wallet" tone="primary" label="可發放"     value={kpi.payable} onClick={()=>navigate('/payouts')}/>
        <KpiCard icon="check"  tone="success" label="30 天已發"   value={kpi.paid30d} suffix={'·  $' + (kpi.paid30dUsd/100).toFixed(0)}/>
        <KpiCard icon="alert"  tone="danger"  label="風險訊號"   value={kpi.risky} onClick={()=>setRiskFilter('suspicious')}/>
      </div>

      <div className="a-card">
        {/* Tabs */}
        <div className="a-tabs" style={{margin:'0 0 -1px',padding:'0 14px'}}>
          {tabs.map(t => (
            <button key={t.key} className={`a-tab ${tab === t.key ? 'active' : ''}`} onClick={()=>{setTab(t.key); window.location.hash = `#/referrals?status=${t.key}`;}}>
              {t.label}
              <span className="a-tab-count">{tabCount(t.key)}</span>
            </button>
          ))}
        </div>

        {/* Filter toolbar */}
        <div className="a-table-toolbar">
          <div className="a-input-group" style={{width:280}}>
            <span className="a-input-group-prefix"><AIcon name="search" size={14}/></span>
            <input className="a-input" placeholder="搜尋 ID / code / Referrer / Referee…" 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>
            <option value="ID">🇮🇩 Indonesia</option>
            <option value="PH">🇵🇭 Philippines</option>
            <option value="VN">🇻🇳 Vietnam</option>
            <option value="TH">🇹🇭 Thailand</option>
          </select>
          <select className="a-select" style={{width:130}} value={riskFilter} onChange={e=>setRiskFilter(e.target.value)}>
            <option value="">所有風險</option>
            <option value="suspicious">⚠️ 風險訊號</option>
          </select>
          <select className="a-select" style={{width:120,marginLeft:'auto'}} value={sortBy} onChange={e=>setSortBy(e.target.value)}>
            <option value="newest">最新優先</option>
            <option value="oldest">最舊優先</option>
            <option value="risk">風險優先</option>
          </select>
          <span className="text-muted fs-13">共 {filtered.length} 筆</span>
        </div>

        {/* Table */}
        <table className="a-table">
          <thead><tr>
            <th style={{width:100}}>ID</th>
            <th style={{width:130}}>Code</th>
            <th>Referrer</th>
            <th>Referee</th>
            <th style={{width:100}}>來源</th>
            <th style={{width:170}}>Gates 進度</th>
            <th style={{width:110}}>狀態</th>
            <th style={{width:80}}>風險</th>
            <th style={{width:110}}>註冊時間</th>
          </tr></thead>
          <tbody>
            {filtered.map(r => {
              const er = ADMIN.candidates.find(c => c.id === r.referrerId);
              const ee = ADMIN.candidates.find(c => c.id === r.refereeId);
              return (
                <tr key={r.id} className="clickable" onClick={()=>setOpenId(r.id)}>
                  <td><Mono>{r.id}</Mono></td>
                  <td>
                    <span style={{
                      display:'inline-flex',alignItems:'center',padding:'2px 7px',
                      background:'#F9F5FF',color:'#6941C6',border:'1px solid #E9D7FE',
                      borderRadius:4,fontFamily:'var(--a-mono)',fontSize:11,fontWeight:600,
                    }}>{r.code}</span>
                  </td>
                  <td>{er ? <CandidateCell c={er} sub={`${er.city} · ${ADMIN.nat[er.nationality].name}`}/> : <span className="text-faint">—</span>}</td>
                  <td>{ee ? <CandidateCell c={ee} sub={`${ee.city} · ${ADMIN.nat[ee.nationality].name}`}/> : <span className="text-faint">—</span>}</td>
                  <td><AttrPill source={r.attributionSource}/></td>
                  <td><GatesPill r={r}/></td>
                  <td><RefStatus status={r.status}/></td>
                  <td>
                    {r.suspicious ? (
                      <span title={r.suspReasons.join(' · ')} style={{display:'inline-flex',alignItems:'center',gap:4,color:'var(--a-warn)',fontSize:12,fontWeight:600}}>
                        <AIcon name="alert" size={13}/> {r.suspReasons.length}
                      </span>
                    ) : <span className="text-faint fs-12">—</span>}
                  </td>
                  <td className="text-muted fs-12">{FMT.rel(r.refereeSignupAt)}</td>
                </tr>
              );
            })}
          </tbody>
        </table>
        {filtered.length === 0 && <AEmpty icon="gift" title="沒有符合條件的 Referral"/>}
      </div>

      {openRef && (
        <ReferralDrawer
          r={openRef}
          onClose={()=>setOpenId(null)}
          onUpdate={(patch, logAction, logDiff) => update(openRef.id, patch, logAction, logDiff)}
          onVoid={(category, reason) => {
            update(openRef.id, {
              status: 'VOID', voidAt: new Date(), voidCategory: category, voidReason: reason,
            }, 'REFERRAL_VOID', { status: {before: openRef.status, after: 'VOID'}, voidCategory: category, reason });
            toast.success('已標記為作廢');
          }}
          onForceApprove={(reason) => {
            update(openRef.id, {
              status: 'PAYABLE', holdEndAt: new Date(), manualApproveReason: reason,
            }, 'REFERRAL_MANUAL_APPROVE', { status: {before: openRef.status, after: 'PAYABLE'}, reason });
            toast.success('已手動核准，狀態轉為可發放');
          }}
          onExtendHold={(days, reason) => {
            const newEnd = new Date(openRef.holdEndAt.getTime() + days * 86400000);
            update(openRef.id, { holdEndAt: newEnd, holdExtendNote: reason }, 'REFERRAL_HOLD_EXTEND', { holdDays: { before: 14, after: 14 + days }, reason });
            toast.success(`HOLD 延長 ${days} 天`);
          }}
        />
      )}
    </div>
  );
};

// ============================================================
// KPI card (reusable)
// ============================================================
const KpiCard = ({ icon, tone, label, value, suffix, onClick }) => {
  const toneMap = {
    neutral: { bg: 'var(--a-paper-2)',     fg: 'var(--a-ink-soft)' },
    warn:    { bg: 'var(--a-warn-bg)',     fg: 'var(--a-warn)' },
    primary: { bg: 'var(--a-primary-50)',  fg: 'var(--a-primary)' },
    success: { bg: 'var(--a-success-bg)',  fg: 'var(--a-success)' },
    danger:  { bg: 'var(--a-danger-bg)',   fg: 'var(--a-danger)' },
    info:    { bg: 'var(--a-info-bg)',     fg: 'var(--a-info)' },
  };
  const t = toneMap[tone] || toneMap.neutral;
  return (
    <div className="a-card" style={{padding:'14px 16px',cursor:onClick?'pointer':'default'}} onClick={onClick}>
      <div style={{display:'flex',alignItems:'center',gap:12}}>
        <div style={{width:36,height:36,borderRadius:8,display:'flex',alignItems:'center',justifyContent:'center',background:t.bg,color:t.fg,flexShrink:0}}>
          <AIcon name={icon} size={16}/>
        </div>
        <div style={{minWidth:0}}>
          <div className="fs-12 text-muted fw-500">{label}</div>
          <div style={{fontSize:22,fontWeight:700,color:'var(--a-ink)',letterSpacing:'-0.01em',lineHeight:1.2}}>
            {value}{suffix && <span style={{fontSize:12,fontWeight:500,color:'var(--a-ink-soft)',marginLeft:6}}>{suffix}</span>}
          </div>
        </div>
      </div>
    </div>
  );
};

// ============================================================
// Steps pill — visual 4-dot progress through the new referral flow:
//   1. Sign up → 2. Resume → 3. Approved → 4. HOLD ends
// Field names mirror admin-data.js + helper-referral-data.js.
// ============================================================
const GatesPill = ({ r, large = false }) => {
  const steps = [
    { key: 1, label: '1 註冊',     done: !!r.refereeSignupAt,    at: r.refereeSignupAt },
    { key: 2, label: '2 完成履歷', done: !!r.resumeSubmittedAt,  at: r.resumeSubmittedAt },
    { key: 3, label: '3 通過審核', done: !!r.approvedAt,         at: r.approvedAt },
    { key: 4, label: '4 HOLD 結束', done: r.holdEndAt && r.holdEndAt < ADMIN.now, at: r.holdEndAt },
  ];
  const sz = large ? 16 : 12;
  return (
    <div style={{display:'flex',alignItems:'center',gap:large?8:5}}>
      {steps.map((g, i) => {
        const done = g.done;
        return (
          <Fragment key={g.key}>
            <div title={g.label + (done ? ' · 完成' : ' · 未完成')} style={{
              width: sz, height: sz, borderRadius: '50%',
              background: done ? 'oklch(0.62 0.14 155)' : 'var(--a-paper-2)',
              border: '1.5px solid ' + (done ? 'oklch(0.52 0.15 155)' : 'var(--a-line)'),
              display:'flex',alignItems:'center',justifyContent:'center',
              color:'#fff',fontSize:large?10:8,fontWeight:700,
              flexShrink: 0,
            }}>
              {done ? '✓' : g.key}
            </div>
            {i < steps.length - 1 && <div style={{flex:'0 0 auto',width:large?18:8,height:1.5,background: steps[i+1].done ? 'oklch(0.62 0.14 155)' : 'var(--a-line)'}}/>}
          </Fragment>
        );
      })}
    </div>
  );
};

// ============================================================
// REFERRAL DRAWER
// ============================================================
const ReferralDrawer = ({ r, onClose, onUpdate, onVoid, onForceApprove, onExtendHold }) => {
  const [showVoid, setShowVoid] = uS(false);
  const [showApprove, setShowApprove] = uS(false);
  const [showExtend, setShowExtend] = uS(false);

  const referrer = ADMIN.candidates.find(c => c.id === r.referrerId);
  const referee  = ADMIN.candidates.find(c => c.id === r.refereeId);

  const cur = referrer ? { ID:'IDR', PH:'PHP', VN:'VND', TH:'THB' }[referrer.nationality] : 'USD';
  const rate = referrer ? { ID:16000, PH:56, VN:24500, TH:35 }[referrer.nationality] : 1;
  const baseUsd = 500;
  const baseLocal = Math.round(baseUsd * rate);

  // Relevant payouts for this referral
  const payouts = ADMIN.payouts.filter(p => p.referralId === r.id);

  return (
    <ADrawer open={true} onClose={onClose} size="lg" title={
      <div style={{display:'flex',alignItems:'center',gap:10}}>
        <AIcon name="gift" size={16}/>
        <span>{r.id}</span>
        <RefStatus status={r.status}/>
        {r.suspicious && (
          <span className="a-badge a-badge-warn" title={r.suspReasons.join(' · ')}>
            <AIcon name="alert" size={11}/> 風險
          </span>
        )}
      </div>
    }>
      {/* HEADER: Referrer ↔ Referee */}
      <div style={{display:'grid',gridTemplateColumns:'1fr auto 1fr',gap:14,alignItems:'center',padding:'4px 0 18px',borderBottom:'1px solid var(--a-line)',marginBottom:18}}>
        <div>
          <div className="fs-11 text-muted mb-6" style={{letterSpacing:'0.06em',textTransform:'uppercase'}}>Referrer</div>
          {referrer ? (
            <a href={`#/candidates/${referrer.id}`} style={{textDecoration:'none',color:'inherit',display:'block'}}>
              <CandidateCell c={referrer}/>
            </a>
          ) : <span className="text-faint">—</span>}
          {referrer && (
            <div className="fs-12 text-muted mt-6" style={{display:'flex',gap:8,alignItems:'center'}}>
              <Mono>{r.code}</Mono>
              <span>·</span>
              <span>{FMT.rel(referrer.createdAt) || '—'}</span>
            </div>
          )}
        </div>
        <div style={{textAlign:'center',color:'var(--a-ink-faint)'}}>
          <AIcon name="arrowRight" size={20}/>
        </div>
        <div>
          <div className="fs-11 text-muted mb-6" style={{letterSpacing:'0.06em',textTransform:'uppercase'}}>Referee</div>
          {referee ? (
            <a href={`#/candidates/${referee.id}`} style={{textDecoration:'none',color:'inherit',display:'block'}}>
              <CandidateCell c={referee}/>
            </a>
          ) : <span className="text-faint">—</span>}
          {referee && (
            <div className="fs-12 text-muted mt-6" style={{display:'flex',gap:8,alignItems:'center'}}>
              <AttrPill source={r.attributionSource}/>
              <span>·</span>
              <span>{FMT.rel(r.refereeSignupAt)}</span>
            </div>
          )}
        </div>
      </div>

      {/* GATES Timeline */}
      <section style={{marginBottom:24}}>
        <h3 className="fs-13 fw-600 mb-10" style={{color:'var(--a-ink)',letterSpacing:'0.02em',textTransform:'uppercase'}}>達成進度</h3>
        <GatesTimeline r={r}/>
      </section>

      {/* BONUS Overview — single total, no milestone breakdown */}
      <section style={{marginBottom:24}}>
        <h3 className="fs-13 fw-600 mb-10" style={{color:'var(--a-ink)',letterSpacing:'0.02em',textTransform:'uppercase'}}>獎金</h3>
        <div className="a-card" style={{padding:16}}>
          <div style={{display:'flex',alignItems:'center',justifyContent:'space-between',gap:14,flexWrap:'wrap'}}>
            <div>
              <div className="fs-11 text-muted fw-600 mb-4" style={{letterSpacing:'0.06em',textTransform:'uppercase'}}>達成後可發放獎金</div>
              <div style={{display:'flex',alignItems:'baseline',gap:10}}>
                <span style={{fontSize:28,fontWeight:700,color:'var(--a-ink)',letterSpacing:'-0.02em'}}>${(baseUsd/100).toFixed(2)}</span>
                <span className="fs-13 text-muted">USD · 參考 {FMT.local(baseLocal, cur)}</span>
              </div>
              <div className="fs-12 text-muted mt-4">依<a href="/plans" style={{color:'var(--a-primary)',textDecoration:'none'}}>方案設定</a>計算 · 通過全部步驟後進入 HOLD 觀察期</div>
            </div>
            <div>
              {payouts.length > 0
                ? <BonusStatus status={payouts[0].status}/>
                : <BonusStatus status={r.status === 'PENDING' ? null : r.status}/>}
            </div>
          </div>
        </div>
      </section>

      {/* RISK Signals */}
      {(r.suspicious || r.sameIpCClass || r.sameDeviceFp) && (
        <section style={{marginBottom:24}}>
          <h3 className="fs-13 fw-600 mb-10" style={{color:'var(--a-ink)',letterSpacing:'0.02em',textTransform:'uppercase'}}>風控訊號</h3>
          <div className="a-card" style={{padding:0,background:r.suspicious?'var(--a-warn-bg)':'var(--a-paper-2)',borderColor:r.suspicious?'var(--a-warn-line)':'var(--a-line)'}}>
            <div style={{padding:'12px 14px'}}>
              <ul style={{listStyle:'none',margin:0,padding:0,display:'flex',flexDirection:'column',gap:8}}>
                {r.suspReasons && r.suspReasons.map((reason, i) => (
                  <li key={i} style={{display:'flex',gap:8,alignItems:'flex-start',fontSize:13}}>
                    <span style={{color:'var(--a-warn)',marginTop:1}}><AIcon name="alert" size={13}/></span>
                    <span>{reason}</span>
                  </li>
                ))}
                {!r.suspicious && (
                  <li className="fs-13 text-muted" style={{display:'flex',gap:8,alignItems:'center'}}>
                    <AIcon name="check" size={13}/> 未觸發風險規則
                  </li>
                )}
              </ul>
            </div>
            <div style={{borderTop:'1px solid var(--a-line)',padding:'10px 14px',display:'grid',gridTemplateColumns:'repeat(3,1fr)',gap:14,fontSize:12}}>
              <div>
                <div className="text-muted fs-11 mb-4">Referee IP</div>
                <Mono>{r.refereeIp}</Mono>
                {r.sameIpCClass && <div className="fs-11 mt-2" style={{color:'var(--a-warn)'}}>⚠ 與 Referrer 同 C-class</div>}
              </div>
              <div>
                <div className="text-muted fs-11 mb-4">裝置指紋</div>
                <Mono>{(r.refereeDeviceFp || '').slice(0,14)}…</Mono>
                {r.sameDeviceFp && <div className="fs-11 mt-2" style={{color:'var(--a-danger)'}}>⚠ 與 Referrer 相同</div>}
              </div>
              <div>
                <div className="text-muted fs-11 mb-4">註冊時間差</div>
                <Mono>{r.signupGapMin ? (r.signupGapMin < 60 ? r.signupGapMin + ' min' : Math.round(r.signupGapMin/60) + ' h') : '—'}</Mono>
                {r.signupGapMin < 10 && <div className="fs-11 mt-2" style={{color:'var(--a-danger)'}}>⚠ 極短間隔</div>}
              </div>
            </div>
          </div>
        </section>
      )}

      {/* VOID / APPROVE state */}
      {r.status === 'VOID' && (
        <section style={{marginBottom:24}}>
          <div className="a-card" style={{padding:14,borderColor:'var(--a-danger-line)',background:'var(--a-danger-bg)'}}>
            <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:6}}>
              <AIcon name="ban" size={14} style={{color:'var(--a-danger)'}}/>
              <span className="fw-600" style={{color:'var(--a-danger)'}}>已作廢</span>
              <span className="a-badge a-badge-danger" style={{marginLeft:'auto'}}>
                {VOID_CATEGORIES.find(c => c.key === r.voidCategory)?.label || r.voidCategory}
              </span>
            </div>
            <div className="fs-13" style={{color:'var(--a-ink-soft)'}}>{r.voidReason}</div>
            <div className="fs-12 text-muted mt-6">作廢於 {FMT.iso(r.voidAt)}</div>
          </div>
        </section>
      )}
      {r.manualApproveReason && (
        <section style={{marginBottom:24}}>
          <div className="a-card" style={{padding:14,borderColor:'var(--a-primary-100)',background:'var(--a-primary-50)'}}>
            <div style={{display:'flex',alignItems:'center',gap:8,marginBottom:6}}>
              <AIcon name="checkCircle" size={14} style={{color:'var(--a-primary)'}}/>
              <span className="fw-600" style={{color:'var(--a-primary)'}}>手動核准</span>
            </div>
            <div className="fs-13" style={{color:'var(--a-ink-soft)'}}>{r.manualApproveReason}</div>
          </div>
        </section>
      )}

      {/* Payouts history — milestone bonus removed, so each referral has at
          most one payout row (the single $5 base bonus). */}
      {payouts.length > 0 && (
        <section style={{marginBottom:24}}>
          <h3 className="fs-13 fw-600 mb-10" style={{color:'var(--a-ink)',letterSpacing:'0.02em',textTransform:'uppercase'}}>發放紀錄</h3>
          <table className="a-table a-table-compact">
            <thead><tr>
              <th>Payout ID</th><th>金額</th><th>狀態</th><th>批次</th><th>轉帳時間</th>
            </tr></thead>
            <tbody>
              {payouts.map(p => (
                <tr key={p.id}>
                  <td><Mono>{p.id}</Mono></td>
                  <td><span className="fs-13 fw-600">{FMT.usd(p.amountUsdCents)}</span>{' '}<span className="text-muted fs-11">({FMT.local(p.amountLocalCents, p.localCurrency)})</span></td>
                  <td><BonusStatus status={p.status}/></td>
                  <td>{p.batchId ? <Mono>{p.batchId}</Mono> : <span className="text-faint">—</span>}</td>
                  <td className="text-muted fs-12">{p.paidAt ? FMT.rel(p.paidAt) : '—'}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </section>
      )}

      {/* ACTIONS */}
      {r.status !== 'VOID' && r.status !== 'PAID' && (
        <section style={{marginTop:24,padding:'14px 0 0',borderTop:'1px solid var(--a-line)'}}>
          <div style={{display:'flex',gap:8,flexWrap:'wrap'}}>
            {r.status === 'HOLD' && (
              <>
                <button className="a-btn a-btn-primary" onClick={()=>setShowApprove(true)}>
                  <AIcon name="check" size={13}/> 手動核准為 PAYABLE
                </button>
                <button className="a-btn a-btn-default" onClick={()=>setShowExtend(true)}>
                  <AIcon name="clock" size={13}/> 延長 HOLD
                </button>
              </>
            )}
            <button className="a-btn a-btn-danger" onClick={()=>setShowVoid(true)} style={{marginLeft:r.status==='HOLD'?'auto':0}}>
              <AIcon name="ban" size={13}/> 作廢此 Referral
            </button>
          </div>
        </section>
      )}

      {showVoid    && <VoidModal    r={r} onClose={()=>setShowVoid(false)} onConfirm={(cat, reason)=>{ onVoid(cat, reason); setShowVoid(false); }}/>}
      {showApprove && <ApproveModal r={r} onClose={()=>setShowApprove(false)} onConfirm={(reason)=>{ onForceApprove(reason); setShowApprove(false); }}/>}
      {showExtend  && <ExtendModal  r={r} onClose={()=>setShowExtend(false)} onConfirm={(days, reason)=>{ onExtendHold(days, reason); setShowExtend(false); }}/>}
    </ADrawer>
  );
};

// ============================================================
// GatesTimeline — detailed timeline inside drawer.
// New 4-step flow (synced with CANDIDATE_REVIEW_FLOW.md):
//   1. Sign up → 2. Complete resume → 3. Approved → 4. HOLD ends
// Then PAID or VOID terminal states.
// ============================================================
const GatesTimeline = ({ r }) => {
  const steps = [
    { key: 'signup',   label: '1 · 註冊',         at: r.refereeSignupAt,   desc: '被介紹人完成帳號註冊',                          done: !!r.refereeSignupAt },
    { key: 'resume',   label: '2 · 完成履歷',     at: r.resumeSubmittedAt, desc: '被介紹人按下 Publish · 履歷送審中',             done: !!r.resumeSubmittedAt, future: !r.resumeSubmittedAt && !!r.approvedAt },
    { key: 'approved', label: '3 · 通過審核',     at: r.approvedAt,        desc: '管理員核准履歷 · 候選人正式上架',               done: !!r.approvedAt,        future: !r.approvedAt && !!r.holdEndAt },
    { key: 'hold',     label: '4 · 14 天 HOLD 結束', at: r.holdEndAt,         desc: '觀察期結束，轉為 PAYABLE 可加入下次批次',  done: r.holdEndAt && r.holdEndAt < ADMIN.now, future: r.holdEndAt && r.holdEndAt > ADMIN.now },
    r.status === 'PAID' && { key: 'paid', label: '已發放', at: r.paidAt, desc: '外部交易 ID: ' + (r.externalTxId || '—'), done: true },
    r.status === 'VOID' && { key: 'void', label: '作廢', at: r.voidAt, desc: r.voidReason, void: true },
  ].filter(Boolean);

  return (
    <div style={{position:'relative'}}>
      <div style={{position:'absolute',left:14,top:14,bottom:14,width:2,background:'var(--a-line)',zIndex:0}}/>
      {steps.map((s, i) => (
        <div key={s.key} style={{position:'relative',display:'flex',gap:14,paddingBottom:i<steps.length-1?18:0,zIndex:1}}>
          <div style={{
            width:30,height:30,borderRadius:'50%',flexShrink:0,
            display:'flex',alignItems:'center',justifyContent:'center',
            background: s.void ? 'var(--a-danger-bg)' : s.done ? (s.bonus ? 'oklch(0.94 0.09 270)' : 'oklch(0.92 0.09 155)') : 'var(--a-paper)',
            border: '2px solid ' + (s.void ? 'var(--a-danger)' : s.done ? (s.bonus ? 'oklch(0.52 0.14 270)' : 'oklch(0.52 0.15 155)') : 'var(--a-line)'),
            color: s.void ? 'var(--a-danger)' : s.done ? (s.bonus ? 'oklch(0.42 0.14 270)' : 'oklch(0.36 0.15 155)') : 'var(--a-ink-faint)',
          }}>
            <AIcon name={s.void ? 'x' : s.done ? 'check' : s.future ? 'clock' : 'clock'} size={13}/>
          </div>
          <div style={{flex:1,paddingTop:4}}>
            <div style={{display:'flex',justifyContent:'space-between',alignItems:'baseline',gap:8}}>
              <div className="fs-13 fw-600" style={{color: s.void ? 'var(--a-danger)' : 'var(--a-ink)'}}>{s.label}</div>
              <div className="fs-12 text-muted" style={{flexShrink:0}}>{s.at ? FMT.iso(s.at) : (s.future ? '預計 ' + FMT.iso(s.at) : '未發生')}</div>
            </div>
            <div className="fs-12 text-muted mt-2" style={{color:'var(--a-ink-soft)'}}>{s.desc}</div>
          </div>
        </div>
      ))}
    </div>
  );
};

// ============================================================
// BonusStatus — small tag
// ============================================================
const BonusStatus = ({ status }) => {
  if (!status) return <span className="a-badge a-badge-neutral">待達成</span>;
  const M = {
    HOLD:    { label: 'HOLD',   kind: 'warn' },
    PAYABLE: { label: '可發放', kind: 'primary' },
    PAID:    { label: '已發放', kind: 'success' },
    VOID:    { label: '作廢',   kind: 'danger' },
    FAILED:  { label: '失敗',   kind: 'danger' },
  };
  const m = M[status] || { label: status, kind: 'neutral' };
  return <span className={`a-badge a-badge-${m.kind}`}>{m.label}</span>;
};

// ============================================================
// VoidModal — pick category + reason
// ============================================================
const VoidModal = ({ r, onClose, onConfirm }) => {
  const [cat, setCat] = uS('SELF_REF');
  const [reason, setReason] = uS('');
  const ok = cat && reason.trim().length >= 4;
  return (
    <AModal open={true} onClose={onClose} title="作廢此 Referral" width={520}>
      <div className="fs-13 mb-14" style={{color:'var(--a-ink-soft)'}}>
        作廢後所有相關 Payout（Base / Milestone）將標記為 VOID，不會發放。此動作會記錄於審計日誌。
      </div>
      <div className="fs-12 fw-600 mb-8" style={{letterSpacing:'0.04em',textTransform:'uppercase',color:'var(--a-ink-muted)'}}>選擇類別</div>
      <div style={{display:'flex',flexDirection:'column',gap:8,marginBottom:14}}>
        {VOID_CATEGORIES.map(c => (
          <label key={c.key} style={{
            display:'flex',gap:10,padding:'9px 12px',borderRadius:6,cursor:'pointer',
            border:'1px solid ' + (cat === c.key ? 'var(--a-primary)' : 'var(--a-line)'),
            background: cat === c.key ? 'var(--a-primary-50)' : 'var(--a-paper)',
          }}>
            <input type="radio" checked={cat === c.key} onChange={()=>setCat(c.key)} style={{marginTop:3,accentColor:'var(--a-primary)'}}/>
            <div>
              <div className="fs-13 fw-600">{c.label}</div>
              <div className="fs-12 text-muted mt-2">{c.desc}</div>
            </div>
          </label>
        ))}
      </div>
      <div className="fs-12 fw-600 mb-6" style={{letterSpacing:'0.04em',textTransform:'uppercase',color:'var(--a-ink-muted)'}}>說明（必填，至少 4 字）</div>
      <textarea className="a-input" rows={3} value={reason} onChange={e=>setReason(e.target.value)} placeholder="例：裝置指紋與 Referrer 相同 + 時間差小於 10 分鐘"/>

      <div style={{display:'flex',gap:8,justifyContent:'flex-end',marginTop:18}}>
        <button className="a-btn a-btn-default" onClick={onClose}>取消</button>
        <button className="a-btn a-btn-danger-solid" disabled={!ok} onClick={()=>onConfirm(cat, reason)}>
          <AIcon name="ban" size={13}/> 確認作廢
        </button>
      </div>
    </AModal>
  );
};

// ============================================================
// ApproveModal — manual approve HOLD → PAYABLE
// ============================================================
const ApproveModal = ({ r, onClose, onConfirm }) => {
  const [reason, setReason] = uS('');
  const ok = reason.trim().length >= 4;
  return (
    <AModal open={true} onClose={onClose} title="手動核准為 PAYABLE" width={480}>
      <div className="fs-13 mb-14" style={{color:'var(--a-ink-soft)'}}>
        此動作會跳過剩餘 HOLD 觀察期，立即將 Referral 轉為 PAYABLE。需填寫理由供稽核。
      </div>
      <div className="a-card" style={{padding:10,marginBottom:12,background:'var(--a-paper-2)'}}>
        <div className="fs-11 text-muted mb-4" style={{letterSpacing:'0.06em',textTransform:'uppercase'}}>原 HOLD 截止</div>
        <Mono className="fs-13">{FMT.iso(r.holdEndAt)}</Mono>
      </div>
      <div className="fs-12 fw-600 mb-6" style={{letterSpacing:'0.04em',textTransform:'uppercase',color:'var(--a-ink-muted)'}}>核准理由（必填）</div>
      <textarea className="a-input" rows={3} value={reason} onChange={e=>setReason(e.target.value)} placeholder="例：Referrer 長期無風險，客服電話確認後加速發放"/>

      <div style={{display:'flex',gap:8,justifyContent:'flex-end',marginTop:18}}>
        <button className="a-btn a-btn-default" onClick={onClose}>取消</button>
        <button className="a-btn a-btn-primary" disabled={!ok} onClick={()=>onConfirm(reason)}>
          <AIcon name="check" size={13}/> 確認核准
        </button>
      </div>
    </AModal>
  );
};

// ============================================================
// ExtendModal — extend HOLD by N days
// ============================================================
const ExtendModal = ({ r, onClose, onConfirm }) => {
  const [days, setDays] = uS(7);
  const [reason, setReason] = uS('');
  const ok = reason.trim().length >= 4 && days >= 1 && days <= 60;
  return (
    <AModal open={true} onClose={onClose} title="延長 HOLD 觀察期" width={460}>
      <div className="fs-13 mb-14" style={{color:'var(--a-ink-soft)'}}>
        將 HOLD 截止時間延後以做進一步觀察。常用於有軟規則觸發但尚未確定作廢的情況。
      </div>
      <div style={{display:'grid',gridTemplateColumns:'100px 1fr',gap:12,alignItems:'center',marginBottom:14}}>
        <label className="fs-13 fw-500">延長天數</label>
        <div style={{display:'flex',alignItems:'center',gap:10}}>
          <input type="number" className="a-input" value={days} onChange={e=>setDays(parseInt(e.target.value)||0)} min={1} max={60} style={{width:90}}/>
          <span className="fs-12 text-muted">天（1–60）</span>
        </div>
        <label className="fs-13 fw-500" style={{alignSelf:'flex-start',paddingTop:6}}>原因</label>
        <textarea className="a-input" rows={3} value={reason} onChange={e=>setReason(e.target.value)} placeholder="例：S1 同 IP 觸發，需額外觀察"/>
      </div>
      <div className="a-card" style={{padding:10,marginBottom:14,background:'var(--a-paper-2)'}}>
        <div className="fs-12 flex justify-between">
          <span className="text-muted">原截止</span><Mono>{FMT.iso(r.holdEndAt)}</Mono>
        </div>
        <div className="fs-12 flex justify-between mt-4" style={{color:'var(--a-primary)',fontWeight:600}}>
          <span>新截止</span><Mono>{FMT.iso(new Date(r.holdEndAt.getTime() + days * 86400000))}</Mono>
        </div>
      </div>
      <div style={{display:'flex',gap:8,justifyContent:'flex-end'}}>
        <button className="a-btn a-btn-default" onClick={onClose}>取消</button>
        <button className="a-btn a-btn-primary" disabled={!ok} onClick={()=>onConfirm(days, reason)}>
          <AIcon name="clock" size={13}/> 延長 HOLD
        </button>
      </div>
    </AModal>
  );
};

// ============================================================
// Audit helper
// ============================================================
const pushLog = (action, targetType, targetId, diff) => {
  ADMIN.auditLogs.unshift({
    id: 'log_' + Date.now(),
    at: new Date(),
    actor: (window._adminUser && window._adminUser.name) || 'Admin',
    action, targetType, targetId, diff,
  });
};

Object.assign(window, {
  ReferralsPageReal, RefStatus, REF_STATUS_MAP, GatesPill, BonusStatus, AttrPill, KpiCard, pushLog,
});
