// admin-pages.jsx — 5 pages: Dashboard, Users, Stats, BetaRequests, Whitelist
const C = window.ADMIN_COLORS;
const { Card, StatCard, Pill, Btn, Input, Select, Tabs } = window.AdminShell;

// ===== Mock data =====
const NOW = new Date('2026-05-04T14:19:38');
const fmt = (d) => d.toLocaleDateString('ko-KR', { year: 'numeric', month: 'numeric', day: 'numeric' });
const fmtTime = (d) => d.toLocaleString('ko-KR', { dateStyle: 'medium', timeStyle: 'short' });

// ===== 공통: 데이터 테이블 =====
function Table({ columns, rows, empty = '데이터 없음' }) {
  return (
    <div style={{ overflowX: 'auto', border: `1px solid ${C.border}`, borderRadius: 10, background: C.panel }}>
      <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 13 }}>
        <thead>
          <tr style={{ background: C.primarySofter }}>
            {columns.map((col, i) => (
              <th key={i} style={{
                textAlign: col.align || 'left',
                padding: '10px 14px',
                fontSize: 11, fontWeight: 700, letterSpacing: '0.04em',
                color: C.muted, textTransform: 'uppercase',
                borderBottom: `1px solid ${C.border}`,
                whiteSpace: 'nowrap',
                width: col.width,
              }}>{col.header}</th>
            ))}
          </tr>
        </thead>
        <tbody>
          {rows.length === 0 ? (
            <tr>
              <td colSpan={columns.length} style={{ padding: '60px 20px', textAlign: 'center', color: C.faint, fontSize: 13 }}>
                {empty}
              </td>
            </tr>
          ) : rows.map((row, i) => (
            <tr key={i} style={{
              borderBottom: i === rows.length - 1 ? 'none' : `1px solid ${C.border}`,
              transition: 'background 0.1s',
            }}
              onMouseEnter={e => e.currentTarget.style.background = C.primarySofter}
              onMouseLeave={e => e.currentTarget.style.background = ''}
            >
              {columns.map((col, j) => (
                <td key={j} style={{
                  padding: '12px 14px',
                  textAlign: col.align || 'left',
                  color: col.muted ? C.muted : C.text,
                  fontFamily: col.mono ? "'JetBrains Mono', ui-monospace, monospace" : undefined,
                  fontSize: col.mono ? 12.5 : 13,
                  verticalAlign: 'middle',
                }}>
                  {col.render ? col.render(row, i) : row[col.key]}
                </td>
              ))}
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}

// ============================================================
// 1. DASHBOARD
// ============================================================
// [β-Phase C-2-4] hardcoded stats/revenue/distribution/recent → props 인터페이스.
// 비주얼 JSX 0 변경 — StatCard grid / 매출 Card / 분포 Card / 최근 이벤트 Card 100% 보존.
// distribution도 props로 (mockup hardcoded Free/Pro/기관 → server kpi 기반).
function Dashboard({ stats = [], revenue = [], distribution = [], recent = [] }) {
  return (
    <>
      {/* KPI 카드 4개 */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: 12, marginBottom: 12 }}>
        {stats.map((s, i) => <StatCard key={i} {...s}/>)}
      </div>

      {/* 매출 + 분포 */}
      <div style={{ display: 'grid', gridTemplateColumns: '2fr 1fr', gap: 12, marginBottom: 12 }}>
        <Card padding={0}>
          <div style={{ padding: '14px 18px', borderBottom: `1px solid ${C.border}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
            <div style={{ fontSize: 12, fontWeight: 700, color: C.muted, letterSpacing: '0.04em', textTransform: 'uppercase' }}>매출</div>
            <Pill tone="mono" size="xs">전월 대비</Pill>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)' }}>
            {revenue.map((r, i) => (
              <div key={i} style={{
                padding: '20px 22px',
                borderRight: i < 2 ? `1px solid ${C.border}` : 'none',
              }}>
                <div style={{ fontSize: 11, color: C.muted, marginBottom: 8 }}>{r.label}</div>
                <div className="mono" style={{ fontSize: 26, fontWeight: 700, color: C.text, letterSpacing: '-0.02em' }}>{r.value}</div>
                <div style={{ fontSize: 11, color: C.faint, marginTop: 6 }}>{r.sub}</div>
              </div>
            ))}
          </div>
        </Card>

        <Card padding={0}>
          <div style={{ padding: '14px 18px', borderBottom: `1px solid ${C.border}` }}>
            <div style={{ fontSize: 12, fontWeight: 700, color: C.muted, letterSpacing: '0.04em', textTransform: 'uppercase' }}>유저 분포</div>
          </div>
          <div style={{ padding: '18px 22px' }}>
            {distribution.map((r, i) => (
              <div key={i} style={{ marginBottom: i === distribution.length - 1 ? 0 : 14 }}>
                <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 12, marginBottom: 5 }}>
                  <span style={{ color: C.muted }}>{r.label}</span>
                  <span className="mono" style={{ color: C.text, fontWeight: 600 }}>{r.val}</span>
                </div>
                <div style={{ height: 6, background: C.rowSoft, borderRadius: 3, overflow: 'hidden' }}>
                  <div style={{ width: `${r.pct}%`, height: '100%', background: r.tone, borderRadius: 3 }}/>
                </div>
              </div>
            ))}
          </div>
        </Card>
      </div>

      {/* 최근 이벤트 */}
      <Card padding={0}>
        <div style={{ padding: '14px 18px', borderBottom: `1px solid ${C.border}`, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
          <div style={{ fontSize: 12, fontWeight: 700, color: C.muted, letterSpacing: '0.04em', textTransform: 'uppercase' }}>최근 이벤트</div>
          <div className="mono" style={{ fontSize: 11, color: C.faint }}>실시간 · audit_log</div>
        </div>
        <div>
          {recent.map((r, i) => (
            <div key={i} style={{
              display: 'grid', gridTemplateColumns: '160px 1fr 1fr 70px',
              gap: 16, padding: '11px 18px',
              borderTop: i === 0 ? 'none' : `1px solid ${C.border}`,
              fontSize: 12.5, alignItems: 'center',
            }}>
              <div className="mono" style={{ color: C.primary, fontWeight: 600 }}>{r.event}</div>
              <div style={{ color: C.text, fontFamily: "'JetBrains Mono', ui-monospace, monospace", fontSize: 12 }}>{r.user}</div>
              <div style={{ color: C.muted }}>{r.meta}</div>
              <div className="mono" style={{ color: C.faint, textAlign: 'right', fontSize: 11.5 }}>{r.time}</div>
            </div>
          ))}
        </div>
      </Card>

      <div className="mono" style={{ fontSize: 10.5, color: C.faint, marginTop: 16, textAlign: 'right' }}>
        업데이트: {fmtTime(NOW)}
      </div>
    </>
  );
}

// ============================================================
// 2. USERS
// ============================================================
// [β-Phase C-3-4] hardcoded → props 인터페이스 변환.
// 비주얼 JSX 0 변경 — 필터 바/Table/Pill/페이지네이션 시각 100% 보존.
// hardcoded fallback default 유지 — mockup standalone 미리보기 시각 호환.
// ⋯ 버튼에 onClick 추가 (시각 변경 X) — 클릭 시 onUserClick(row) 호출.
// 페이지네이션 시각은 비활성 그대로 (서버 페이지네이션 별도 phase).
function Users({
  search: searchProp,
  onSearch,
  tier: tierProp,
  onTierChange,
  sort: sortProp,
  onSortChange,
  users: usersProp,
  total: totalProp,
  counts: countsProp,
  status: statusProp,
  onStatusChange,
  onUserClick,
}) {
  const [innerSearch, setInnerSearch] = React.useState('');
  const [innerTier,   setInnerTier]   = React.useState('all');
  const [innerStatus, setInnerStatus] = React.useState('all');
  const [innerSort,   setInnerSort]   = React.useState('newest');
  const search    = searchProp !== undefined ? searchProp : innerSearch;
  const setSearch = onSearch || setInnerSearch;
  const tier      = tierProp !== undefined ? tierProp : innerTier;
  const setTier   = onTierChange || setInnerTier;
  const status    = statusProp !== undefined ? statusProp : innerStatus;
  const setStatus = onStatusChange || setInnerStatus;
  const sort      = sortProp !== undefined ? sortProp : innerSort;
  const setSort   = onSortChange || setInnerSort;

  const allUsers = usersProp || [
    { email: '2261071@pcu.ac.kr', role: 'user', mbti: '—', tier: 'free', credits: 0, joined: '2026.5.4.', status: '활성' },
    { email: 'byeongjun0021@naver.com', role: 'user', mbti: '—', tier: 'free', credits: 0, joined: '2026.5.4.', status: '활성' },
    { email: 'mybj0610@gmail.com', role: 'user', mbti: '—', tier: 'free', credits: 0, joined: '2026.5.2.', status: '활성' },
    { email: 'memnyan35@gmail.com', role: 'admin', mbti: 'vibe_trader', tier: 'free', credits: 22, joined: '2026.5.2.', status: '활성' },
  ];
  const total = totalProp !== undefined ? totalProp : allUsers.length;
  const counts = countsProp || { all: 4, free: 4, pro: 0, org: 0 };

  /* props 모드면 서버 필터링 가정 (allUsers 그대로 표시).
     standalone 모드는 클라이언트 필터링 — mockup 미리보기 호환. */
  const filtered = usersProp ? allUsers : (
    allUsers
      .filter(u => !search || u.email.toLowerCase().includes(search.toLowerCase()))
      .filter(u => tier === 'all' || u.tier === tier)
  );

  return (
    <>
      {/* 필터 바 */}
      <Card padding={16} style={{ marginBottom: 12 }}>
        <div style={{ display: 'grid', gridTemplateColumns: '1fr auto auto', gap: 10, alignItems: 'center' }}>
          <Input icon="🔍" value={search} onChange={e => setSearch(e.target.value)} placeholder="이메일 검색"/>
          <Select value={sort} onChange={e => setSort(e.target.value)} options={[
            { value: 'newest', label: '가입일 ↓ 내림차순' },
            { value: 'oldest', label: '가입일 ↑ 오름차순' },
            { value: 'credits', label: '크레딧 순' },
          ]}/>
          <Btn kind="default">CSV 내보내기</Btn>
        </div>
        {/* KEITTA-ADMIN-UI-PORT: 상태 Select(더미 onChange) → 탭(전체/활성/차단/탈퇴), filter_status 연동. 등급 행과 별개. */}
        <div style={{ marginTop: 12, display: 'flex', alignItems: 'center', gap: 12 }}>
          <span style={{ fontSize: 11, color: C.muted, fontWeight: 700, letterSpacing: '0.04em', textTransform: 'uppercase' }}>상태</span>
          <Tabs value={status} onChange={setStatus} tabs={[
            { value: 'all', label: '전체' },
            { value: 'active', label: '활성' },
            { value: 'banned', label: '차단' },
            { value: 'deleted', label: '탈퇴' },
          ]}/>
        </div>
        <div style={{ marginTop: 12, display: 'flex', alignItems: 'center', gap: 12 }}>
          <span style={{ fontSize: 11, color: C.muted, fontWeight: 700, letterSpacing: '0.04em', textTransform: 'uppercase' }}>등급</span>
          <Tabs value={tier} onChange={setTier} tabs={[
            { value: 'all', label: '전체', count: counts.all },
            { value: 'free', label: 'Free', count: counts.free },
            { value: 'pro', label: 'Pro', count: counts.pro },
            { value: 'org', label: '기관', count: counts.org },
          ]}/>
        </div>
      </Card>

      <Table
        columns={[
          { header: '', width: 32, render: () => <input type="checkbox" style={{ accentColor: C.primary }}/> },
          { header: '이메일', key: 'email', mono: true, render: r => <span style={{ color: C.primary, fontWeight: 600 }}>{r.email}</span> },
          { header: 'Role', render: r => <Pill tone={r.role === 'admin' ? 'primary' : 'mono'} size="xs">{r.role}</Pill> },
          { header: 'MBTI', render: r => <span className="mono" style={{ fontSize: 11.5, color: r.mbti === '—' ? C.faint : C.text }}>{r.mbti}</span> },
          { header: '구독', render: r => <Pill tone="mono" size="xs">{r.tier}</Pill> },
          { header: '크레딧', mono: true, align: 'right', render: r => <span style={{ color: r.credits > 0 ? C.text : C.faint }}>{r.credits}</span> },
          { header: '총 판단', mono: true, align: 'right', render: r => <span style={{ color: r.judgmentCount != null ? C.text : C.faint }}>{r.judgmentCount != null ? r.judgmentCount : '—'}</span> },
          { header: '마지막 활동', mono: true, render: r => <span style={{ color: C.muted, fontSize: 12 }}>{r.lastActivity ? new Date(r.lastActivity).toLocaleDateString('ko-KR') : '—'}</span> },
          { header: '가입 경로', render: r => r.signupPath === 'beta' ? <Pill tone="primary" size="xs">베타</Pill> : r.signupPath === 'general' ? <Pill tone="mono" size="xs">일반</Pill> : <span style={{ color: C.faint }}>—</span> },
          { header: '가입일', mono: true, render: r => <span style={{ color: C.muted, fontSize: 12 }}>{r.joined}</span> },
          { header: '상태', render: r => <Pill tone="good" size="xs">● {r.status}</Pill> },
          { header: '', width: 60, align: 'right', render: r => <button onClick={() => onUserClick && onUserClick(r)} style={{ background: 'transparent', border: 0, color: C.faint, cursor: 'pointer', fontSize: 16 }}>⋯</button> },
        ]}
        rows={filtered}
        empty="검색 결과 없음"
      />

      {/* 페이지네이션 */}
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 16, fontSize: 12, color: C.muted }}>
        <span>{filtered.length}명 / 총 {total}명</span>
        <div style={{ display: 'flex', gap: 4 }}>
          <button style={{ width: 28, height: 28, border: `1px solid ${C.border}`, background: C.panel, borderRadius: 6, color: C.faint, cursor: 'not-allowed' }}>‹</button>
          <button style={{ width: 28, height: 28, border: `1px solid ${C.primary}`, background: C.primary, borderRadius: 6, color: 'white', fontWeight: 700 }}>1</button>
          <button style={{ width: 28, height: 28, border: `1px solid ${C.border}`, background: C.panel, borderRadius: 6, color: C.faint, cursor: 'not-allowed' }}>›</button>
        </div>
      </div>
    </>
  );
}

// ============================================================
// 3. STATS
// ============================================================
function MiniLineChart({ data, color, height = 120 }) {
  const W = 800, H = height;
  const max = Math.max(...data, 1);
  const pts = data.map((v, i) => [i * (W / (data.length - 1)), H - (v / max) * (H - 20) - 10]);
  const path = pts.map((p, i) => (i === 0 ? `M ${p[0]} ${p[1]}` : `L ${p[0]} ${p[1]}`)).join(' ');
  const area = `M 0 ${H} L ${pts.map(p => `${p[0]} ${p[1]}`).join(' L ')} L ${W} ${H} Z`;
  return (
    <svg viewBox={`0 0 ${W} ${H}`} style={{ width: '100%', height: H, display: 'block' }}>
      <defs>
        <linearGradient id={`grad-${color.replace('#','')}`} x1="0" y1="0" x2="0" y2="1">
          <stop offset="0%" stopColor={color} stopOpacity="0.18"/>
          <stop offset="100%" stopColor={color} stopOpacity="0"/>
        </linearGradient>
      </defs>
      {/* 그리드 */}
      {[0.25, 0.5, 0.75].map((t, i) => (
        <line key={i} x1={0} y1={H * t} x2={W} y2={H * t} stroke="var(--c-row-soft)" strokeWidth="1"/>
      ))}
      <path d={area} fill={`url(#grad-${color.replace('#','')})`}/>
      <path d={path} stroke={color} strokeWidth="2" fill="none" strokeLinecap="round" strokeLinejoin="round"/>
      {pts.filter((_, i) => i === pts.length - 1).map((p, i) => (
        <circle key={i} cx={p[0]} cy={p[1]} r="4" fill={color}/>
      ))}
    </svg>
  );
}

function ChartCard({ title, value, change, data, color, xLabels }) {
  return (
    <Card padding={0}>
      <div style={{ padding: '14px 18px', borderBottom: `1px solid ${C.border}`, display: 'flex', justifyContent: 'space-between', alignItems: 'baseline' }}>
        <div>
          <div style={{ fontSize: 11, fontWeight: 700, color: C.muted, letterSpacing: '0.04em', textTransform: 'uppercase' }}>{title}</div>
          <div style={{ display: 'flex', gap: 10, alignItems: 'baseline', marginTop: 6 }}>
            <span className="mono" style={{ fontSize: 22, fontWeight: 700, color: C.text, letterSpacing: '-0.02em' }}>{value}</span>
            {change !== undefined && (
              <span style={{ fontSize: 12, fontWeight: 700, color: change >= 0 ? C.good : C.bad }}>
                {change >= 0 ? '▲' : '▼'} {Math.abs(change)}%
              </span>
            )}
          </div>
        </div>
        <div style={{ display: 'flex', gap: 6, fontSize: 11, color: C.faint }} className="mono">
          <span>min {Math.min(...data)}</span>
          <span>·</span>
          <span>max {Math.max(...data)}</span>
        </div>
      </div>
      <div style={{ padding: '12px 18px 14px' }}>
        <MiniLineChart data={data} color={color}/>
        <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: 10, color: C.faint, marginTop: 4 }} className="mono">
          {xLabels.map((l, i) => <span key={i}>{l}</span>)}
        </div>
      </div>
    </Card>
  );
}

// [β-Phase C-3-2] hardcoded → props 인터페이스 변환.
// JSX/시각 디테일 0 변경 — Tabs/ChartCard/보조통계 카드 100% 보존.
// hardcoded fallback default 유지 — mockup standalone 미리보기 시각 호환.
function Stats({
  range: rangeProp,
  onRangeChange,
  signups: signupsProp,
  judges: judgesProp,
  xLabels: xLabelsProp,
  signupsValue,
  signupsChange,
  judgesValue,
  judgesChange,
}) {
  const [innerRange, setInnerRange] = React.useState('30d');
  const range    = rangeProp !== undefined ? rangeProp : innerRange;
  const setRange = onRangeChange || setInnerRange;
  const signups   = signupsProp || [3, 2.8, 2.6, 2.5, 2.4, 2.3, 2.2, 2.2, 2.2, 2.2, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1, 2.1];
  const judges    = judgesProp  || [2, 1, 1.5, 0.5, 1, 2, 3, 4, 1, 0.5, 0.5, 1.5, 2, 1, 0.5, 1, 2, 3, 2.5, 2, 1.5, 1, 1.5, 2, 2.5, 3, 3.5, 3, 4.5, 4.5];
  const xLabels30 = xLabelsProp || ['2026-04-04', '2026-04-11', '2026-04-18', '2026-04-25', '2026-05-04'];
  const sV = signupsValue  !== undefined ? signupsValue  : 62;
  const sC = signupsChange !== undefined ? signupsChange : 8;
  const jV = judgesValue   !== undefined ? judgesValue   : 58;
  const jC = judgesChange  !== undefined ? judgesChange  : 24;

  return (
    <>
      <div style={{ display: 'flex', gap: 10, alignItems: 'center', marginBottom: 12 }}>
        <Tabs value={range} onChange={setRange} tabs={[
          { value: '1d', label: '일별' },
          { value: '30d', label: '30일' },
          { value: '90d', label: '90일' },
          { value: '1y', label: '1년' },
        ]}/>
        <span style={{ flex: 1 }}/>
        <Btn kind="default" size="sm">CSV 내려받기</Btn>
      </div>

      <div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
        <ChartCard title="신규 가입자" value={sV} change={sC} data={signups} color={C.primary} xLabels={xLabels30}/>
        <ChartCard title="Judge 호출" value={jV} change={jC} data={judges} color={C.primaryDeep} xLabels={xLabels30}/>
      </div>

      {/* 보조 통계 */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(180px, 1fr))', gap: 12, marginTop: 12 }}>
        {[
          { label: '평균 세션', value: '4분 22초', hint: 'judgments timestamp 추정' },
          { label: '재방문율', value: '36%', hint: '7일 기준' },
          { label: 'D7 리텐션', value: '14%', hint: 'cohort 분석' },
          { label: 'API 응답', value: '184ms', hint: 'p50' },
        ].map((s, i) => (
          <Card key={i} padding={16}>
            <div style={{ fontSize: 11, color: C.muted, fontWeight: 700, letterSpacing: '0.04em', textTransform: 'uppercase', marginBottom: 6 }}>{s.label}</div>
            <div className="mono" style={{ fontSize: 20, fontWeight: 700, color: C.text }}>{s.value}</div>
            <div style={{ fontSize: 10.5, color: C.faint, marginTop: 4 }} className="mono">{s.hint}</div>
          </Card>
        ))}
      </div>

      <div style={{ marginTop: 20, padding: '14px 18px', background: C.primarySofter, borderRadius: 10, fontSize: 12, color: C.textSoft, lineHeight: 1.6 }}>
        구독 시계열은 별도 RPC 메뷰로 변경 — 추후 통합 예정.
      </div>
    </>
  );
}

// ============================================================
// 4. BETA REQUESTS
// ============================================================
// [β-Phase C-2-2] hardcoded all → props 인터페이스 변환.
// 비주얼 JSX 0 변경 — Tabs/Btn/Table/Pill 디테일 100% 보존.
// tab + selected는 자체 state 유지 (UI 책임). counts는 rows에서 자체 계산.
function BetaRequests({ rows = [], onApprove, onReject, onBatchApprove, onBatchReject }) {
  const [tab, setTab] = React.useState('pending');
  const [selected, setSelected] = React.useState(new Set());

  const counts = {
    pending: rows.filter(r => r.status === 'pending').length,
    approved: rows.filter(r => r.status === 'approved').length,
    rejected: rows.filter(r => r.status === 'rejected').length,
    all: rows.length,
  };
  const filtered = tab === 'all' ? rows : rows.filter(r => r.status === tab);

  const toggleSelect = (id) => {
    const next = new Set(selected);
    if (next.has(id)) next.delete(id); else next.add(id);
    setSelected(next);
  };

  const handleBatchApprove = () => {
    if (selected.size === 0) return;
    if (onBatchApprove) onBatchApprove(Array.from(selected));
    setSelected(new Set());
  };
  const handleBatchReject = () => {
    if (selected.size === 0) return;
    if (onBatchReject) onBatchReject(Array.from(selected));
    setSelected(new Set());
  };

  const statusPill = (s) => {
    if (s === 'pending') return <Pill tone="warn" size="xs">● 대기</Pill>;
    if (s === 'approved') return <Pill tone="good" size="xs">● 승인</Pill>;
    return <Pill tone="bad" size="xs">● 거부</Pill>;
  };

  return (
    <>
      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 12, flexWrap: 'wrap', gap: 10 }}>
        <Tabs value={tab} onChange={setTab} tabs={[
          { value: 'pending', label: '대기', count: counts.pending },
          { value: 'approved', label: '승인', count: counts.approved },
          { value: 'rejected', label: '거부', count: counts.rejected },
          { value: 'all', label: '전체', count: counts.all },
        ]}/>
        <div style={{ display: 'flex', gap: 8 }}>
          <Btn kind="default" size="sm" onClick={handleBatchReject} disabled={selected.size === 0}>선택 거부</Btn>
          <Btn kind="primary" size="sm" onClick={handleBatchApprove} disabled={selected.size === 0}>선택 승인</Btn>
        </div>
      </div>

      <Table
        columns={[
          { header: '', width: 32, render: r => <input type="checkbox" checked={selected.has(r.id)} onChange={() => r.status === 'pending' && toggleSelect(r.id)} style={{ accentColor: C.primary }}/> },
          { header: '이메일', mono: true, render: r => <span style={{ color: C.primary, fontWeight: 600 }}>{r.email}</span> },
          { header: '이름', render: r => <span style={{ fontWeight: 500 }}>{r.name}</span> },
          { header: '신청 이유', render: r => <span style={{ color: C.muted, fontSize: 12.5 }}>{r.reason}</span> },
          { header: '신청일', mono: true, render: r => <span style={{ color: C.muted, fontSize: 11.5 }}>{r.date}</span> },
          { header: '상태', render: r => statusPill(r.status) },
          { header: '액션', align: 'right', render: r => r.status === 'pending' ? (
            <div style={{ display: 'flex', gap: 6, justifyContent: 'flex-end' }}>
              <Btn kind="danger" size="sm" onClick={() => onReject && onReject(r.id)}>거부</Btn>
              <Btn kind="primary" size="sm" onClick={() => onApprove && onApprove(r.id)}>승인</Btn>
            </div>
          ) : <span style={{ fontSize: 11, color: C.faint }} className="mono">처리됨</span> },
        ]}
        rows={filtered}
        empty={tab === 'pending' ? '대기 중인 신청자가 없습니다' : '신청자 없음'}
      />
    </>
  );
}

// ============================================================
// 5. WHITELIST
// ============================================================
// [β-Phase C-2-1] hardcoded rows → props 인터페이스 변환.
// 비주얼 JSX 0 변경 — form/Table/Card/footer 디테일 100% 보존.
// form 자체 state (email/domain/validity/memo)는 mockup 패턴 그대로 유지.
function Whitelist({ rows = [], onAdd, onDelete, onCsvUpload }) {
  const [email, setEmail] = React.useState('');
  const [domain, setDomain] = React.useState('');
  const [validity, setValidity] = React.useState('forever');
  const [memo, setMemo] = React.useState('');
  const csvInputRef = React.useRef(null);

  const handleSubmit = (e) => {
    e.preventDefault();
    if (!email && !domain) return;
    if (onAdd) onAdd({ email, domain, validity, memo });
    setEmail(''); setDomain(''); setValidity('forever'); setMemo('');
  };

  const handleCsvBtn = () => {
    if (csvInputRef.current) csvInputRef.current.click();
  };

  const handleCsvFile = (e) => {
    const file = e.target.files && e.target.files[0];
    if (!file) return;
    const reader = new FileReader();
    reader.onload = () => {
      try {
        const text = String(reader.result || '');
        const lines = text.split(/\r?\n/).filter(l => l.trim());
        if (lines.length === 0) { alert('CSV 비어 있음'); return; }
        const headers = lines[0].split(',').map(h => h.trim().toLowerCase());
        const csvRows = lines.slice(1).map(line => {
          const cols = line.split(',').map(c => c.trim());
          const obj = {};
          headers.forEach((h, i) => { if (cols[i]) obj[h] = cols[i]; });
          return obj;
        });
        if (onCsvUpload) onCsvUpload(csvRows);
      } catch (err) {
        alert('CSV 파싱 실패: ' + err.message);
      } finally {
        e.target.value = '';
      }
    };
    reader.onerror = () => alert('파일 읽기 실패');
    reader.readAsText(file);
  };

  return (
    <>
      <div style={{ display: 'flex', justifyContent: 'flex-end', marginBottom: 12 }}>
        <Btn kind="default" size="sm" onClick={handleCsvBtn}>📄 CSV 일괄 업로드</Btn>
        <input ref={csvInputRef} type="file" accept=".csv,text/csv" onChange={handleCsvFile} style={{ display: 'none' }}/>
      </div>

      {/* 추가 폼 */}
      <Card padding={18} style={{ marginBottom: 16 }}>
        <div style={{ fontSize: 11, color: C.muted, fontWeight: 700, letterSpacing: '0.04em', textTransform: 'uppercase', marginBottom: 12 }}>새 항목 추가</div>
        <form onSubmit={handleSubmit} style={{ display: 'grid', gridTemplateColumns: '1.4fr 1.2fr 1fr 1.6fr auto', gap: 10, alignItems: 'center' }}>
          <Input value={email} onChange={e => setEmail(e.target.value)} placeholder="email (단일 이메일)"/>
          <Input value={domain} onChange={e => setDomain(e.target.value)} placeholder="domain (예: keitta.com)"/>
          <Select value={validity} onChange={e => setValidity(e.target.value)} options={[
            { value: 'forever', label: '연장 ⓜ 무기한' },
            { value: '30d', label: '30일' },
            { value: '90d', label: '90일' },
            { value: '1y', label: '1년' },
          ]}/>
          <Input value={memo} onChange={e => setMemo(e.target.value)} placeholder="메모 (선택)"/>
          <Btn kind="primary" type="submit">+ 추가</Btn>
        </form>
        <div style={{ fontSize: 11, color: C.faint, marginTop: 10 }}>
          email 또는 domain 중 하나만 입력. domain을 등록하면 해당 도메인 모든 이메일이 자동 통과합니다.
        </div>
      </Card>

      <Table
        columns={[
          { header: 'Email', mono: true, render: r => r.email === '—' ? <span style={{ color: C.faint }}>—</span> : <span style={{ color: C.primary, fontWeight: 600 }}>{r.email}</span> },
          { header: 'Domain', mono: true, render: r => r.domain === '—' ? <span style={{ color: C.faint }}>—</span> : <span className="mono" style={{ color: C.text, fontWeight: 600 }}>{r.domain}</span> },
          { header: '출처', render: r => <Pill tone={r.source === 'csv' ? 'primary' : 'mono'} size="xs">{r.source}</Pill> },
          { header: '추가일', mono: true, render: r => <span style={{ color: C.muted, fontSize: 11.5 }}>{r.addedAt}</span> },
          { header: '만료일', mono: true, render: r => <span style={{ color: r.expires === '무기한' ? C.faint : C.text, fontSize: 11.5 }}>{r.expires}</span> },
          { header: '메모', render: r => <span style={{ color: C.muted, fontSize: 12.5 }}>{r.memo}</span> },
          { header: '', width: 60, align: 'right', render: r => <Btn kind="ghost" size="sm" onClick={() => onDelete && onDelete(r.id)}>삭제</Btn> },
        ]}
        rows={rows}
      />

      <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginTop: 16, fontSize: 12, color: C.muted }}>
        <span className="mono">전체 {rows.length}건 · 활성 {rows.filter(r => r.expires === '무기한' || r.expires > '2026.5.4.').length}건</span>
        <span style={{ fontSize: 11, color: C.faint }} className="mono">테이블: whitelist · 인덱스 (email, domain)</span>
      </div>
    </>
  );
}

window.AdminPages = { Dashboard, Users, Stats, BetaRequests, Whitelist };
