// screens-pool-ops.jsx — 接真后端的彩票运营三屏
// - PoolControlScreen  /api/dashboard + /api/pool/heatmap + /api/draws + /api/draws/{id}/control + force-draw
// - TrendDetectionScreen  /api/draws/{lottery} (history) — 全前端冷热 / 长龙 / 走势计算
// - WinbackScreen  /api/finance/flow + /api/admin/events/recent + /api/admin/notif/broadcast

const _poolFetch = window.NebulaAdmin.adminFetch;
const _poolPoll  = window.NebulaAdmin.useAdminPoll;

// ─── helpers ────────────────────────────────────────────────────────────────
function _yuan(n, t) {
  if (n == null || isNaN(n)) return '¥0';
  const v = Number(n);
  const tx = (t && t.tx) ? t.tx : ((zh) => zh);
  if (Math.abs(v) >= 10000) return '¥' + (v / 10000).toFixed(2) + tx('万', 'k');
  return '¥' + Math.round(v).toLocaleString();
}
function _maskUid(uid) {
  if (!uid) return '—';
  const s = String(uid);
  if (s.length <= 4) return s;
  if (s.length <= 8) return s.slice(0, 2) + '***' + s.slice(-2);
  return s.slice(0, 4) + '****' + s.slice(-3);
}
function _playLabel(playID, t) {
  if (!playID) return '—';
  const tx = (t && t.tx) ? t.tx : ((zh) => zh);
  const map = {
    'big': tx('大', 'Big'), 'small': tx('小', 'Small'), 'odd': tx('单', 'Odd'), 'even': tx('双', 'Even'),
    'dragon': tx('龙', 'Dragon'), 'tiger': tx('虎', 'Tiger'), 'tie': tx('和', 'Tie'),
    'sum': tx('和值', 'Sum'), 'straight': tx('直选', 'Direct'), 'group': tx('组选', 'Group'),
  };
  return map[playID] || playID;
}
function _fmtClock(s) {
  if (s == null || isNaN(s)) return '--:--';
  const v = Math.max(0, Math.floor(s));
  const m = Math.floor(v / 60), r = v % 60;
  return String(m).padStart(2, '0') + ':' + String(r).padStart(2, '0');
}
// imbalance 着色规则：≤0.2 绿 / 0.2-0.5 橙 / >0.5 红
function _imbColor(score) {
  if (score == null) return 'var(--text-muted)';
  if (score <= 0.2) return 'var(--success)';
  if (score <= 0.5) return 'var(--warning)';
  return 'var(--danger)';
}
function _imbLabel(score, t) {
  if (score == null) return '—';
  const tx = (t && t.tx) ? t.tx : ((zh) => zh);
  if (score <= 0.2) return tx('盘口均衡', 'Balanced');
  if (score <= 0.5) return tx('轻度偏斜', 'Slight skew');
  return tx('严重失衡 · 重点关注', 'Severe imbalance · attention');
}

// ════════════════════════════════════════════════════════════════════════════
// PAGE 1 · 控盘 / 盘口分析
// ════════════════════════════════════════════════════════════════════════════
function PoolControlScreen({ t, push }) {
  const [tick, setTick] = React.useState(0);
  const bump = () => setTick(x => x + 1);

  const [dashboard] = _poolPoll('/api/dashboard?_=' + tick, 8000);
  const [heatmap]   = _poolPoll('/api/pool/heatmap?_=' + tick, 5000);
  const [draws]     = _poolPoll('/api/draws?_=' + tick, 6000);

  // KPI 条
  const d = dashboard || {};

  return (
    <div className="stack">
      <div className="page-h">
        <div>
          <h1>{t.tx('盘口分析 / 控盘', 'Book Analytics / Pool Control')}</h1>
          <div className="sub">{t.tx('实时盘口失衡 · 大户集中度 · 控盘操作', 'Real-time imbalance · whale concentration · pool ops')}</div>
        </div>
        <div className="actions">
          <span className="text-muted" style={{ fontSize: 12 }}>
            {d.generated_at ? t.tx('更新于 ', 'Updated ') + new Date(d.generated_at).toLocaleTimeString('zh-CN', { hour12: false }) : t.tx('加载中…', 'Loading…')}
          </span>
          <button className="btn btn-sm" onClick={bump}>{t.tx('刷新', 'Refresh')}</button>
        </div>
      </div>

      {/* KPI 顶条 */}
      <div className="row-4">
        <KPI label={t.tx('今日流水', 'Today Stake')} value={_yuan(d.today_stake, t)} delta={(d.today_bets || 0) + t.tx(' 笔', ' bets')} spark={<MiniBars data={[1,2,3,4,5,6]} w={100} h={24} />} />
        <KPI label={t.tx('今日派彩', 'Today Payout')} value={_yuan(d.today_payout, t)} delta={(d.today_settled || 0) + t.tx(' 已结', ' settled')} spark={<MiniBars data={[2,3,2,4,3,5]} w={100} h={24} color="oklch(0.65 0.14 30)" />} />
        <KPI label={t.tx('今日毛利', 'Today Profit')} value={_yuan(d.today_profit, t)} up={(d.today_profit || 0) >= 0} delta={(d.today_profit || 0) >= 0 ? t.tx('盈', 'Profit') : t.tx('亏', 'Loss')} spark={<Sparkline data={[0, d.today_profit || 0]} w={100} h={24} stroke={(d.today_profit || 0) >= 0 ? 'oklch(0.6 0.16 145)' : 'oklch(0.65 0.18 30)'} />} />
        <KPI label={t.tx('在线彩种', 'Active Lotteries')} value={String(d.active_lotteries || 0)} delta={(d.today_pending || 0) + t.tx(' 待结', ' pending')} spark={<MiniBars data={[1,1,1,1,1,1]} w={100} h={24} color="oklch(0.6 0.13 230)" />} />
      </div>

      {/* 失衡热力 */}
      <HeatmapPanel heatmap={heatmap} t={t} />

      {/* 控盘面板（彩种列表 + 操作）*/}
      <ControlPanel draws={draws} push={push} onChange={bump} t={t} />
    </div>
  );
}

function HeatmapPanel({ heatmap, t }) {
  if (!heatmap) {
    return (
      <div className="card"><div className="card-h"><h3>{t.tx('盘口失衡', 'Pool Imbalance')}</h3><span className="meta">{t.tx('加载中…', 'Loading…')}</span></div>
        <div style={{ padding: 18, color: 'var(--text-faint)', fontSize: 12 }}>{t.tx('正在加载 /api/pool/heatmap…', 'Loading /api/pool/heatmap…')}</div>
      </div>
    );
  }
  const score = heatmap.imbalance_score || 0;
  const color = _imbColor(score);
  const label = _imbLabel(score, t);
  const plays = (heatmap.by_play || []);
  const maxStake = plays.reduce((m, p) => Math.max(m, p.total_stake || 0), 0) || 1;
  const users = (heatmap.by_user_concentration || []).slice(0, 10);
  const totalUserStake = users.reduce((s, u) => s + (u.stake || 0), 0) || 1;

  return (
    <div className="card">
      <div className="card-h">
        <h3>{t.tx('盘口失衡热力', 'Imbalance Heatmap')} · {heatmap.lottery || 'cqssc'} · {t.tx('期', 'Period')} {heatmap.current_period || '—'}</h3>
        <span className="meta">{t.tx('每 5s 自动刷新', 'Auto refresh every 5s')}</span>
      </div>
      <div style={{ padding: 14, display: 'grid', gridTemplateColumns: '220px 1fr', gap: 18 }}>
        {/* 失衡分数大圆 */}
        <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 14, background: 'var(--bg-inset)', borderRadius: 12 }}>
          <div className="text-muted" style={{ fontSize: 11, marginBottom: 6 }}>{t.tx('失衡分数 · 0-1', 'Imbalance · 0-1')}</div>
          <div className="tabular" style={{ fontSize: 44, fontWeight: 800, color, lineHeight: 1 }}>{score.toFixed(2)}</div>
          <div style={{ marginTop: 8, fontSize: 12, fontWeight: 600, color }}>{label}</div>
          {/* 进度条 */}
          <div style={{ width: '100%', height: 8, background: 'var(--bg-active)', borderRadius: 100, marginTop: 12, position: 'relative', overflow: 'hidden' }}>
            <div style={{ position: 'absolute', left: 0, top: 0, bottom: 0, width: Math.min(100, score * 100) + '%', background: color }} />
            <span style={{ position: 'absolute', left: '20%', top: 0, bottom: 0, width: 1, background: 'rgba(0,0,0,.2)' }} />
            <span style={{ position: 'absolute', left: '50%', top: 0, bottom: 0, width: 1, background: 'rgba(0,0,0,.2)' }} />
          </div>
          <div className="text-faint" style={{ fontSize: 10, marginTop: 4 }}>{t.tx('≤0.2 均衡 / 0.5 警戒', '≤0.2 balanced / 0.5 alert')}</div>
        </div>

        {/* by_play 横向柱状 */}
        <div>
          <div className="text-muted" style={{ fontSize: 11, fontWeight: 600, marginBottom: 8 }}>{t.tx('各玩法投注分布（按累计金额）', 'Stake distribution by play (cumulative)')}</div>
          {plays.length === 0 && <div className="text-faint" style={{ fontSize: 12, padding: 12 }}>{t.tx('本期尚无投注', 'No bets this period')}</div>}
          {plays.slice(0, 10).map((p, i) => {
            const pct = (p.total_stake || 0) / maxStake;
            const isHot = pct > 0.7;
            return (
              <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 5 }}>
                <div style={{ width: 64, fontSize: 12, fontWeight: 500 }}>{_playLabel(p.play_id, t)}</div>
                <div style={{ flex: 1, height: 14, background: 'var(--bg-active)', borderRadius: 4, position: 'relative', overflow: 'hidden' }}>
                  <div style={{ position: 'absolute', left: 0, top: 0, bottom: 0, width: (pct * 100) + '%', background: isHot ? 'var(--danger)' : pct > 0.4 ? 'var(--warning)' : 'var(--accent)' }} />
                </div>
                <div className="tabular" style={{ width: 90, textAlign: 'right', fontSize: 11.5 }}>
                  {_yuan(p.total_stake, t)}
                  <span className="text-faint" style={{ marginLeft: 4 }}>· {p.bets_count || 0}{t.tx(' 笔', ' bets')}</span>
                </div>
              </div>
            );
          })}
        </div>
      </div>

      {/* 大户集中度 top-10 */}
      <div style={{ padding: '0 14px 14px' }}>
        <div className="text-muted" style={{ fontSize: 11, fontWeight: 600, margin: '4px 0 6px' }}>{t.tx('大户集中度 · Top 10', 'Whale Concentration · Top 10')}</div>
        <div className="tbl-scroll">
        <table className="tbl tbl-stack">
          <thead><tr><th>#</th><th>{t.tx('用户', 'User')}</th><th className="right">{t.tx('投注额', 'Stake')}</th><th>{t.tx('占比', 'Share')}</th></tr></thead>
          <tbody>
            {users.length === 0 && <tr><td colSpan={4} className="text-faint" style={{ fontSize: 12, padding: 12 }}>{t.tx('无大户数据', 'No whale data')}</td></tr>}
            {users.map((u, i) => {
              const pct = (u.stake || 0) / totalUserStake;
              return (
                <tr key={u.user_id || i}>
                  <td data-label="#" className="tabular text-muted">{i + 1}</td>
                  <td data-label={t.tx('用户', 'User')} className="text-mono" style={{ fontSize: 11.5 }}>{_maskUid(u.user_id)}</td>
                  <td data-label={t.tx('投注额', 'Stake')} className="right tabular">{_yuan(u.stake, t)}</td>
                  <td data-label={t.tx('占比', 'Share')}>
                    <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
                      <span className="barmini" style={{ width: 80, height: 5 }}>
                        <i style={{ width: Math.min(100, pct * 100) + '%', background: pct > 0.3 ? 'var(--danger)' : pct > 0.15 ? 'var(--warning)' : 'var(--accent)' }} />
                      </span>
                      <span className="tabular text-muted" style={{ fontSize: 11 }}>{(pct * 100).toFixed(1)}%</span>
                    </div>
                  </td>
                </tr>
              );
            })}
          </tbody>
        </table>
        </div>
      </div>
    </div>
  );
}

function ControlPanel({ draws, push, onChange, t }) {
  const list = (draws && draws.lotteries) || [];
  if (!list.length) {
    return (
      <div className="card"><div className="card-h"><h3>{t.tx('控盘操作', 'Pool Control')}</h3><span className="meta">{t.tx('加载中…', 'Loading…')}</span></div>
        <div style={{ padding: 18, color: 'var(--text-faint)', fontSize: 12 }}>{t.tx('正在加载 /api/draws…', 'Loading /api/draws…')}</div>
      </div>
    );
  }
  return (
    <div className="card">
      <div className="card-h"><h3>{t.tx('控盘操作', 'Pool Control')}</h3><span className="meta">{list.length}{t.tx(' 个彩种', ' lotteries')}</span></div>
      <div style={{ padding: 0 }}>
        {list.map(l => <LotteryControlRow key={l.lottery} lottery={l} push={push} onChange={onChange} t={t} />)}
      </div>
    </div>
  );
}

function LotteryControlRow({ lottery, push, onChange, t }) {
  const [detail, setDetail] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [opsOpen, setOpsOpen] = React.useState(false);

  const fetchDetail = React.useCallback(() => {
    _poolFetch('/api/draws/' + lottery.lottery)
      .then(d => setDetail(d))
      .catch(() => {});
  }, [lottery.lottery]);

  React.useEffect(() => { fetchDetail(); }, [fetchDetail]);
  React.useEffect(() => {
    const i = setInterval(fetchDetail, 6000);
    return () => clearInterval(i);
  }, [fetchDetail]);

  const cur = (detail && detail.current) || {};
  const status = cur.status || 'live';

  const doAction = async (action, needNote) => {
    const labels = {
      pause: t.tx('暂停当期', 'Pause'),
      resume: t.tx('恢复', 'Resume'),
      reopen: t.tx('重开本期', 'Reopen'),
      'flag-anomaly': t.tx('标记异常', 'Flag anomaly'),
    };
    if (!confirm(t.tx(`确认对 ${lottery.name} 执行：${labels[action] || action}？`, `Confirm ${labels[action] || action} on ${lottery.name}?`))) return;
    let note = '';
    if (needNote) {
      note = window.prompt(t.tx('备注（可选）：', 'Note (optional):'), '') || '';
    }
    setBusy(true);
    try {
      await _poolFetch(`/api/draws/${lottery.lottery}/control`, {
        method: 'POST',
        body: JSON.stringify({ action, note }),
      });
      push(t.tx(`${lottery.name} · ${labels[action]} 成功`, `${lottery.name} · ${labels[action]} done`));
      fetchDetail();
      onChange && onChange();
    } catch (e) {
      push(t.tx(`操作失败：${e.status || ''} ${e.body?.error || ''}`, `Action failed: ${e.status || ''} ${e.body?.error || ''}`));
    } finally {
      setBusy(false);
    }
  };

  const doForce = async () => {
    if (!confirm(t.tx(`强制立即开奖 ${lottery.name} 当前期？此操作不可撤销并立即结算所有投注。`, `Force-draw current period of ${lottery.name}? This is irreversible and settles all bets immediately.`))) return;
    setBusy(true);
    try {
      await _poolFetch(`/api/draws/${lottery.lottery}/force-draw`, { method: 'POST', body: JSON.stringify({}) });
      push(t.tx(`${lottery.name} 已强制开奖`, `${lottery.name} force-drawn`));
      fetchDetail();
      onChange && onChange();
    } catch (e) {
      push(t.tx(`强制开奖失败：${e.status || ''} ${e.body?.error || ''}`, `Force-draw failed: ${e.status || ''} ${e.body?.error || ''}`));
    } finally {
      setBusy(false);
    }
  };

  const statusChip = (() => {
    if (status === 'paused') return <span className="badge warning">{t.tx('已暂停', 'Paused')}</span>;
    if (status === 'flagged') return <span className="badge danger">{t.tx('已标记异常', 'Flagged')}</span>;
    if (status === 'anomaly') return <span className="badge danger">{t.tx('异常', 'Anomaly')}</span>;
    return <span className="badge accent">{t.tx('运行中', 'Running')}</span>;
  })();

  const ops = (detail && detail.ops) || [];

  return (
    <div style={{ padding: '12px 16px', borderBottom: '1px solid var(--border)' }}>
      <div style={{ display: 'flex', alignItems: 'center', gap: 14, flexWrap: 'wrap' }}>
        <div style={{ minWidth: 130 }}>
          <div style={{ fontSize: 14, fontWeight: 700 }}>{lottery.name}</div>
          <div className="text-muted text-mono" style={{ fontSize: 11 }}>{lottery.lottery} · {lottery.frequency}</div>
        </div>
        <div style={{ minWidth: 110 }}>
          <div className="text-muted" style={{ fontSize: 10 }}>{t.tx('当前期', 'Period')}</div>
          <div className="text-mono" style={{ fontSize: 13, fontWeight: 600 }}>{cur.period || lottery.current_period || '—'}</div>
        </div>
        <div style={{ minWidth: 84 }}>
          <div className="text-muted" style={{ fontSize: 10 }}>{t.tx('状态', 'Status')}</div>
          <div style={{ marginTop: 2 }}>{statusChip}</div>
        </div>
        <div style={{ minWidth: 84 }}>
          <div className="text-muted" style={{ fontSize: 10 }}>{t.tx('距封盘', 'Close in')}</div>
          <CountdownLabelPool seconds={cur.close_seconds} />
        </div>
        <div style={{ minWidth: 90 }}>
          <div className="text-muted" style={{ fontSize: 10 }}>{t.tx('本期投注', 'Bets')}</div>
          <div className="tabular" style={{ fontSize: 13, fontWeight: 600 }}>{cur.bets_count || 0}{t.tx(' 笔', ' bets')}</div>
        </div>
        <div style={{ minWidth: 110 }}>
          <div className="text-muted" style={{ fontSize: 10 }}>{t.tx('本期流水', 'Stake')}</div>
          <div className="tabular" style={{ fontSize: 13, fontWeight: 600 }}>{_yuan(cur.stake, t)}</div>
        </div>
        <div style={{ flex: 1 }} />
        <div style={{ display: 'flex', gap: 6 }}>
          {status === 'paused'
            ? <button className="btn btn-sm" disabled={busy} onClick={() => doAction('resume', true)}>{t.tx('恢复', 'Resume')}</button>
            : <button className="btn btn-sm" disabled={busy} onClick={() => doAction('pause', true)}>{t.tx('暂停', 'Pause')}</button>}
          <button className="btn btn-sm" disabled={busy} onClick={() => doAction('flag-anomaly', true)}>{t.tx('标记异常', 'Flag anomaly')}</button>
          <button className="btn btn-pri btn-sm" disabled={busy} onClick={doForce}>{t.tx('立即开奖', 'Force draw')}</button>
          <button className="btn btn-sm" onClick={() => setOpsOpen(v => !v)}>{opsOpen ? t.tx('收起', 'Collapse') : t.tx(`日志 (${ops.length})`, `Log (${ops.length})`)}</button>
        </div>
      </div>
      {opsOpen && (
        <div style={{ marginTop: 10, padding: 10, background: 'var(--bg-inset)', borderRadius: 8 }}>
          <div className="text-muted" style={{ fontSize: 11, fontWeight: 600, marginBottom: 6 }}>{t.tx('控盘操作历史 · 最近 10 条', 'Control history · last 10')}</div>
          {ops.length === 0 && <div className="text-faint" style={{ fontSize: 11 }}>{t.tx('暂无操作记录', 'No operations')}</div>}
          {ops.slice(0, 10).map((op, i) => (
            <div key={i} style={{ display: 'flex', gap: 10, padding: '4px 0', borderTop: i > 0 ? '1px solid var(--border)' : 'none', fontSize: 11 }}>
              <span className="text-mono text-muted" style={{ width: 140 }}>{new Date(op.at).toLocaleString('zh-CN', { hour12: false })}</span>
              <span className={'badge ' + (op.op === 'pause' ? 'warning' : op.op === 'flag-anomaly' ? 'danger' : 'accent')} style={{ fontSize: 10 }}>
                {({ pause: t.tx('暂停', 'Pause'), resume: t.tx('恢复', 'Resume'), reopen: t.tx('重开', 'Reopen'), 'flag-anomaly': t.tx('标记异常', 'Flag anomaly') })[op.op] || op.op}
              </span>
              <span style={{ minWidth: 60 }}>{op.by || '—'}</span>
              <span className="text-faint" style={{ flex: 1 }}>{op.note || '—'}</span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

function CountdownLabelPool({ seconds }) {
  const [s, setS] = React.useState(seconds || 0);
  React.useEffect(() => { setS(seconds || 0); }, [seconds]);
  React.useEffect(() => {
    const i = setInterval(() => setS(v => Math.max(0, v - 1)), 1000);
    return () => clearInterval(i);
  }, []);
  return (
    <div className="tabular" style={{ fontSize: 13, fontWeight: 700, color: s < 30 ? 'var(--danger)' : 'var(--text)' }}>
      {_fmtClock(s)}
    </div>
  );
}

// ════════════════════════════════════════════════════════════════════════════
// PAGE 2 · 走势 · 长龙 · 冷热（全前端从 history 算）
// ════════════════════════════════════════════════════════════════════════════
function TrendDetectionScreen({ t, push }) {
  const [active, setActive] = React.useState('cqssc');
  const [tick, setTick] = React.useState(0);
  const [draws] = _poolPoll('/api/draws?_=' + tick, 10000);
  const [detail] = _poolPoll(`/api/draws/${active}?_=${tick}`, 8000);

  const list = (draws && draws.lotteries) || [];
  const history = (detail && detail.history) || [];
  const recent = history.slice(0, 50); // 最近 50 期

  return (
    <div className="stack">
      <div className="page-h">
        <div>
          <h1>{t.tx('走势 · 长龙 · 冷热分析', 'Trends · Streaks · Cold-hot')}</h1>
          <div className="sub">{t.tx('从开奖历史派生 · 无后端复杂查询', 'Derived from draw history · no heavy backend queries')}</div>
        </div>
        <div className="actions">
          <span className="text-muted" style={{ fontSize: 12 }}>{t.tx('当前期', 'Period')} {detail?.current?.period || '—'} · {t.tx('历史', 'History')} {history.length}{t.tx(' 期', ' draws')}</span>
          <button className="btn btn-sm" onClick={() => setTick(x => x + 1)}>{t.tx('刷新', 'Refresh')}</button>
        </div>
      </div>

      {/* 彩种选择 */}
      <div className="tabs">
        {(list.length ? list : [{ lottery: active, name: active }]).map(l => (
          <div key={l.lottery} className={'tab' + (active === l.lottery ? ' active' : '')}
               onClick={() => setActive(l.lottery)}>
            {l.name || l.lottery} {l.frequency ? `· ${l.frequency}` : ''}
          </div>
        ))}
      </div>

      {!detail && <div className="card" style={{ padding: 18, color: 'var(--text-faint)', fontSize: 12 }}>{t.tx('加载历史中…', 'Loading history…')}</div>}

      {detail && (
        <React.Fragment>
          {/* 冷热数字 */}
          <HotColdPanel history={recent} t={t} />

          {/* 长龙检测 */}
          <DragonPanel history={recent} t={t} />

          {/* 走势矩阵图 */}
          <TrendMatrix history={recent.slice(0, 30)} t={t} />
        </React.Fragment>
      )}
    </div>
  );
}

// 统计 history 各数字出现频率
function _digitFreq(history) {
  const freq = {};
  for (const h of history) {
    const nums = h.numbers || [];
    for (const n of nums) {
      const k = String(n);
      freq[k] = (freq[k] || 0) + 1;
    }
  }
  return Object.entries(freq)
    .map(([n, c]) => ({ n, c }))
    .sort((a, b) => b.c - a.c);
}

function HotColdPanel({ history, t }) {
  const freq = _digitFreq(history);
  const hot = freq.slice(0, 5);
  const cold = freq.slice(-5).reverse();
  const maxC = hot[0]?.c || 1;

  return (
    <div className="card">
      <div className="card-h"><h3>{t.tx('冷热数字', 'Cold-hot Digits')} · {t.tx('近', 'Last')} {history.length}{t.tx(' 期', ' draws')}</h3><span className="meta">{t.tx('基于 history.numbers', 'Based on history.numbers')}</span></div>
      <div style={{ padding: 14, display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 18 }}>
        <div>
          <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--danger)', marginBottom: 8 }}>{t.tx('🔥 热号 Top 5', '🔥 Hot Top 5')}</div>
          {hot.length === 0 && <div className="text-faint" style={{ fontSize: 12 }}>{t.tx('无数据', 'No data')}</div>}
          {hot.map(r => (
            <div key={r.n} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 0' }}>
              <span className="text-mono" style={{ width: 30, height: 30, borderRadius: '50%', background: 'var(--danger-soft, oklch(0.95 0.08 25))', color: 'var(--danger)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700, fontSize: 14 }}>{r.n}</span>
              <span className="barmini" style={{ flex: 1, height: 6 }}><i style={{ width: (r.c / maxC * 100) + '%', background: 'var(--danger)' }} /></span>
              <span className="tabular" style={{ width: 60, textAlign: 'right', fontSize: 12 }}>{r.c}{t.tx(' 次', 'x')}</span>
            </div>
          ))}
        </div>
        <div>
          <div style={{ fontSize: 12, fontWeight: 600, color: 'var(--info)', marginBottom: 8 }}>{t.tx('❄ 冷号 Bottom 5', '❄ Cold Bottom 5')}</div>
          {cold.length === 0 && <div className="text-faint" style={{ fontSize: 12 }}>{t.tx('无数据', 'No data')}</div>}
          {cold.map(r => (
            <div key={r.n} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '6px 0' }}>
              <span className="text-mono" style={{ width: 30, height: 30, borderRadius: '50%', background: 'var(--info-soft, oklch(0.95 0.06 230))', color: 'var(--info)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontWeight: 700, fontSize: 14 }}>{r.n}</span>
              <span className="barmini" style={{ flex: 1, height: 6 }}><i style={{ width: (r.c / maxC * 100) + '%', background: 'var(--info)' }} /></span>
              <span className="tabular" style={{ width: 60, textAlign: 'right', fontSize: 12 }}>{r.c}{t.tx(' 次', 'x')}</span>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// 长龙检测：基于"和值大小"和"和值单双"扫历史
function _sumOf(nums) { return (nums || []).reduce((a, b) => a + (Number(b) || 0), 0); }

function _detectDragons(history) {
  // history 按时间倒序（最新在前）。为了"开始期号"语义清晰，先反转为升序
  const asc = history.slice().reverse();
  if (asc.length === 0) return [];
  const dragons = [];

  // 时时彩 5 位 0-9，sum 0-45，>=23 为大
  const isBig = (s) => s >= 23;
  const isOdd = (s) => s % 2 !== 0;

  const scan = (labelFn, kind) => {
    let runVal = null, runLen = 0, runStart = null, prevPeriod = null;
    for (const h of asc) {
      const s = _sumOf(h.numbers);
      const v = labelFn(s);
      if (v === runVal) {
        runLen++;
      } else {
        if (runLen >= 5 && runVal != null) {
          dragons.push({ kind, value: runVal, length: runLen, startPeriod: runStart, endPeriod: prevPeriod, active: false });
        }
        runVal = v; runLen = 1; runStart = h.period;
      }
      prevPeriod = h.period;
    }
    if (runLen >= 5 && runVal != null) {
      dragons.push({ kind, value: runVal, length: runLen, startPeriod: runStart, endPeriod: prevPeriod, active: true });
    }
  };

  scan(s => isBig(s) ? '大' : '小', '大小');
  scan(s => isOdd(s) ? '单' : '双', '单双');

  // 排序：活跃的优先，再按长度倒序
  dragons.sort((a, b) => (b.active - a.active) || (b.length - a.length));
  return dragons;
}

function DragonPanel({ history, t }) {
  const dragons = React.useMemo(() => _detectDragons(history), [history]);
  const kindMap = { '大小': t.tx('大小', 'Big/Small'), '单双': t.tx('单双', 'Odd/Even') };
  const valMap = {
    '大': t.tx('大', 'Big'), '小': t.tx('小', 'Small'),
    '单': t.tx('单', 'Odd'), '双': t.tx('双', 'Even'),
  };
  return (
    <div className="card">
      <div className="card-h">
        <h3>{t.tx('长龙检测 · 连续 ≥5 期同向', 'Streaks · ≥5 periods same direction')}</h3>
        <span className="meta">{t.tx('从历史和值派生 · 大小 / 单双', 'Derived from history sums · Big-Small / Odd-Even')}</span>
      </div>
      <div className="tbl-scroll">
      <table className="tbl tbl-stack">
        <thead><tr><th>{t.tx('类型', 'Type')}</th><th>{t.tx('方向', 'Side')}</th><th>{t.tx('长度', 'Length')}</th><th>{t.tx('起始期号', 'Start')}</th><th>{t.tx('结束期号', 'End')}</th><th>{t.tx('状态', 'Status')}</th></tr></thead>
        <tbody>
          {dragons.length === 0 && <tr><td colSpan={6} className="text-faint" style={{ padding: 14, fontSize: 12 }}>{t.tx('未检测到 ≥5 期长龙', 'No streak ≥5 detected')}</td></tr>}
          {dragons.map((d, i) => (
            <tr key={i}>
              <td data-label={t.tx('类型', 'Type')}>{kindMap[d.kind] || d.kind}</td>
              <td data-label={t.tx('方向', 'Side')}><span className={'badge ' + (d.length >= 10 ? 'danger' : d.length >= 7 ? 'warning' : 'accent')}>{valMap[d.value] || d.value}</span></td>
              <td data-label={t.tx('长度', 'Length')} className="tabular" style={{ fontWeight: 700, color: d.length >= 10 ? 'var(--danger)' : d.length >= 7 ? 'var(--warning)' : 'var(--text)' }}>{d.length}{t.tx(' 期', ' draws')}</td>
              <td data-label={t.tx('起始期号', 'Start')} className="text-mono" style={{ fontSize: 11 }}>{d.startPeriod || '—'}</td>
              <td data-label={t.tx('结束期号', 'End')} className="text-mono" style={{ fontSize: 11 }}>{d.endPeriod || '—'}</td>
              <td data-label={t.tx('状态', 'Status')}>{d.active ? <span className="badge danger">{t.tx('进行中', 'Active')}</span> : <span className="badge outline">{t.tx('已结束', 'Ended')}</span>}</td>
            </tr>
          ))}
        </tbody>
      </table>
      </div>
    </div>
  );
}

function TrendMatrix({ history, t }) {
  // history 倒序（新→旧）；展示最近 30 期，最新在右
  const asc = history.slice().reverse();
  if (asc.length === 0) {
    return <div className="card"><div className="card-h"><h3>{t.tx('走势矩阵', 'Trend Matrix')}</h3></div><div style={{ padding: 14, color: 'var(--text-faint)', fontSize: 12 }}>{t.tx('无数据', 'No data')}</div></div>;
  }
  // 兼容多种 numbers 长度（cqssc=5, k3=3, pk10=10）
  const positionCount = Math.max(...asc.map(h => (h.numbers || []).length));
  const positions = Array.from({ length: positionCount }, (_, i) => t.tx(`位 ${i + 1}`, `Pos ${i + 1}`));

  return (
    <div className="card">
      <div className="card-h">
        <h3>{t.tx('走势矩阵', 'Trend Matrix')} · {t.tx('最近', 'Last')} {asc.length}{t.tx(' 期', ' draws')}</h3>
        <span className="meta">{t.tx('每行 = 一期 · 列 = 各位号码 · 末行最新', 'Row = period · Col = digit position · Bottom = newest')}</span>
      </div>
      <div className="tbl-scroll" style={{ padding: 14 }}>
        <table className="tbl tbl-stack" style={{ minWidth: 600 }}>
          <thead>
            <tr>
              <th style={{ width: 90 }}>{t.tx('期号', 'Period')}</th>
              {positions.map((p, i) => <th key={i} className="tabular" style={{ textAlign: 'center', minWidth: 30 }}>{p}</th>)}
              <th className="right">{t.tx('和值', 'Sum')}</th>
              <th>{t.tx('大小', 'Big/Small')}</th>
              <th>{t.tx('单双', 'Odd/Even')}</th>
            </tr>
          </thead>
          <tbody>
            {asc.map((h, ri) => {
              const nums = h.numbers || [];
              const s = _sumOf(nums);
              const big = s >= 23;
              const odd = s % 2 !== 0;
              return (
                <tr key={h.period || ri}>
                  <td data-label={t.tx('期号', 'Period')} className="text-mono" style={{ fontSize: 10 }}>{String(h.period || '').slice(-6)}</td>
                  {Array.from({ length: positionCount }).map((_, ci) => {
                    const v = nums[ci];
                    if (v == null) return <td key={ci} data-label={positions[ci]}></td>;
                    return (
                      <td key={ci} data-label={positions[ci]} style={{ textAlign: 'center', padding: 2 }}>
                        <span className="text-mono" style={{
                          display: 'inline-flex', width: 22, height: 22, borderRadius: 4,
                          background: ri === asc.length - 1 ? 'var(--accent)' : 'var(--bg-inset)',
                          color: ri === asc.length - 1 ? 'white' : 'var(--text)',
                          alignItems: 'center', justifyContent: 'center', fontSize: 11, fontWeight: 700
                        }}>{v}</span>
                      </td>
                    );
                  })}
                  <td data-label={t.tx('和值', 'Sum')} className="right tabular" style={{ fontWeight: 600 }}>{s}</td>
                  <td data-label={t.tx('大小', 'Big/Small')}><span className={'badge ' + (big ? 'danger' : 'info')} style={{ fontSize: 10 }}>{big ? t.tx('大', 'Big') : t.tx('小', 'Small')}</span></td>
                  <td data-label={t.tx('单双', 'Odd/Even')}><span className={'badge ' + (odd ? 'warning' : 'accent')} style={{ fontSize: 10 }}>{odd ? t.tx('单', 'Odd') : t.tx('双', 'Even')}</span></td>
                </tr>
              );
            })}
          </tbody>
        </table>
      </div>
    </div>
  );
}

// ════════════════════════════════════════════════════════════════════════════
// PAGE 3 · 追损 / 客户关怀
// ════════════════════════════════════════════════════════════════════════════
function WinbackScreen({ t, push }) {
  const [tick, setTick] = React.useState(0);
  const [flow]   = _poolPoll('/api/finance/flow?_=' + tick, 10000);
  // 拉两段事件：bet 和 win，派生净亏
  const [betEvents] = _poolPoll('/api/admin/events/recent?kind=bet&limit=500&_=' + tick, 15000);
  const [winEvents] = _poolPoll('/api/admin/events/recent?kind=bet_won&limit=500&_=' + tick, 15000);

  const week = (flow && flow.week) || { deposit: 0, withdraw: 0, bet: 0, payout: 0, adjust: 0 };
  const today = (flow && flow.today) || { deposit: 0, withdraw: 0, bet: 0, payout: 0, adjust: 0 };

  // 派生深亏用户：从 events 聚合 user_id 的 bet 金额（绝对值）与 win 金额，净亏 = bet - win
  const losers = React.useMemo(() => {
    const m = {};
    const upsert = (uid, key, amt, ts) => {
      if (!uid) return;
      const r = m[uid] || (m[uid] = { user_id: uid, bet: 0, win: 0, betCount: 0, lastTs: 0 });
      r[key] += Math.abs(Number(amt) || 0);
      if (key === 'bet') r.betCount++;
      const t = ts ? new Date(ts).getTime() : 0;
      if (t > r.lastTs) r.lastTs = t;
    };
    for (const e of ((betEvents && betEvents.events) || [])) upsert(e.user_id, 'bet', e.amount, e.ts);
    for (const e of ((winEvents && winEvents.events) || [])) upsert(e.user_id, 'win', e.amount, e.ts);
    const list = Object.values(m)
      .map(r => ({ ...r, netLoss: r.bet - r.win }))
      .filter(r => r.netLoss > 0)
      .sort((a, b) => b.netLoss - a.netLoss)
      .slice(0, 20);
    return list;
  }, [betEvents, winEvents]);

  // 本周每日 deposit vs payout：从 flow.recent_entries 中过滤近 7 日
  const entries = (flow && flow.recent_entries) || [];
  const daily = React.useMemo(() => {
    const days = [];
    const now = new Date();
    for (let i = 6; i >= 0; i--) {
      const d = new Date(now); d.setDate(now.getDate() - i); d.setHours(0, 0, 0, 0);
      days.push({ key: d.toISOString().slice(0, 10), label: (d.getMonth() + 1) + '/' + d.getDate(), deposit: 0, payout: 0 });
    }
    const byKey = Object.fromEntries(days.map(d => [d.key, d]));
    for (const e of entries) {
      const k = e.ts ? new Date(e.ts).toISOString().slice(0, 10) : null;
      if (!k || !byKey[k]) continue;
      const amt = Math.abs(Number(e.amount) || 0);
      if (e.type === 'deposit') byKey[k].deposit += amt;
      else if (e.type === 'win' || e.type === 'refund') byKey[k].payout += amt;
    }
    return days;
  }, [entries]);

  return (
    <div className="stack">
      <div className="page-h">
        <div>
          <h1>{t.tx('追损 · 客户关怀', 'Winback · Customer Care')}</h1>
          <div className="sub">{t.tx('从财务流水 + 事件流派生深亏用户名单', 'Derive deep-loss user list from finance flow + event stream')}</div>
        </div>
        <div className="actions">
          <span className="text-muted" style={{ fontSize: 12 }}>{t.tx('本周流水', 'Week stake')} {_yuan(week.bet, t)} · {t.tx('派彩', 'Payout')} {_yuan(week.payout, t)}</span>
          <button className="btn btn-sm" onClick={() => setTick(x => x + 1)}>{t.tx('刷新', 'Refresh')}</button>
        </div>
      </div>

      {/* 本周 KPI */}
      <div className="row-4">
        <KPI label={t.tx('本周充值', 'Week Deposit')} value={_yuan(week.deposit, t)} delta={t.tx('今日 ', 'Today ') + _yuan(today.deposit, t)} spark={<MiniBars data={daily.map(d => d.deposit || 0.1)} w={100} h={24} />} />
        <KPI label={t.tx('本周提现', 'Week Withdraw')} value={_yuan(week.withdraw, t)} delta={t.tx('今日 ', 'Today ') + _yuan(today.withdraw, t)} spark={<MiniBars data={[1,2,3,2,3,2,3]} w={100} h={24} color="oklch(0.65 0.14 30)" />} />
        <KPI label={t.tx('本周投注', 'Week Stake')} value={_yuan(week.bet, t)} delta={t.tx('今日 ', 'Today ') + _yuan(today.bet, t)} spark={<MiniBars data={[2,3,4,3,4,3,5]} w={100} h={24} color="oklch(0.6 0.13 230)" />} />
        <KPI label={t.tx('本周派彩', 'Week Payout')} value={_yuan(week.payout, t)} delta={t.tx('今日 ', 'Today ') + _yuan(today.payout, t)} spark={<MiniBars data={daily.map(d => d.payout || 0.1)} w={100} h={24} color="oklch(0.6 0.16 145)" />} />
      </div>

      {/* 深亏用户名单 */}
      <LosersTable losers={losers} push={push} t={t} />

      {/* 本周每日柱图 */}
      <DailyCompareChart daily={daily} t={t} />
    </div>
  );
}

function LosersTable({ losers, push, t }) {
  const [sending, setSending] = React.useState({});

  const doCare = async (uid) => {
    const title = window.prompt(t.tx('关怀消息标题：', 'Care message title:'), t.tx('专属关怀礼包', 'Exclusive care gift'));
    if (!title) return;
    const body = window.prompt(t.tx(`发给 ${_maskUid(uid)} 的关怀内容：`, `Care message for ${_maskUid(uid)}:`), t.tx('注意到您最近运气不太顺，已为您发放专属返利券，请查收。', "We've noticed your recent streak — an exclusive rebate coupon has been issued to your wallet."));
    if (!body) return;
    setSending(s => ({ ...s, [uid]: true }));
    try {
      await _poolFetch('/api/admin/notif/send', {
        method: 'POST',
        body: JSON.stringify({ user_id: uid, kind: 'care', title, body, link: '/wallet' }),
      });
      push(t.tx(`已发送关怀 · ${_maskUid(uid)}`, `Care sent · ${_maskUid(uid)}`));
    } catch (e) {
      push(t.tx(`发送失败：${e.status || ''} ${e.body?.error || ''}`, `Send failed: ${e.status || ''} ${e.body?.error || ''}`));
    } finally {
      setSending(s => ({ ...s, [uid]: false }));
    }
  };

  return (
    <div className="card">
      <div className="card-h">
        <h3>{t.tx('深度亏损用户 · Top', 'Deep-loss Users · Top')} {losers.length}</h3>
        <span className="meta">{t.tx('从 events: bet vs bet_won 派生', 'Derived from events: bet vs bet_won')}</span>
      </div>
      <div className="tbl-scroll">
      <table className="tbl tbl-stack">
        <thead><tr><th>#</th><th>{t.tx('用户', 'User')}</th><th className="right">{t.tx('净亏损', 'Net Loss')}</th><th className="right">{t.tx('投注次数', 'Bets')}</th><th className="right">{t.tx('投注总额', 'Total Stake')}</th><th className="right">{t.tx('中奖总额', 'Total Win')}</th><th>{t.tx('最近活跃', 'Last Active')}</th><th></th></tr></thead>
        <tbody>
          {losers.length === 0 && <tr><td colSpan={8} className="text-faint" style={{ padding: 14, fontSize: 12 }}>{t.tx('暂无亏损用户数据（等待事件流加载）', 'No loss data yet (waiting for event stream)')}</td></tr>}
          {losers.map((r, i) => (
            <tr key={r.user_id}>
              <td data-label="#" className="tabular text-muted">{i + 1}</td>
              <td data-label={t.tx('用户', 'User')} className="text-mono" style={{ fontSize: 11.5 }}>{_maskUid(r.user_id)}</td>
              <td data-label={t.tx('净亏损', 'Net Loss')} className="right tabular" style={{ color: 'var(--danger)', fontWeight: 700 }}>-{_yuan(r.netLoss, t)}</td>
              <td data-label={t.tx('投注次数', 'Bets')} className="right tabular">{r.betCount}</td>
              <td data-label={t.tx('投注总额', 'Total Stake')} className="right tabular text-muted">{_yuan(r.bet, t)}</td>
              <td data-label={t.tx('中奖总额', 'Total Win')} className="right tabular text-muted">{_yuan(r.win, t)}</td>
              <td data-label={t.tx('最近活跃', 'Last Active')} className="text-faint text-mono" style={{ fontSize: 10 }}>
                {r.lastTs ? new Date(r.lastTs).toLocaleString('zh-CN', { hour12: false }) : '—'}
              </td>
              <td data-label={t.tx('操作', 'Actions')} className="tbl-actions">
                <button className="btn btn-xs btn-pri" disabled={sending[r.user_id]} onClick={() => doCare(r.user_id)}>
                  {sending[r.user_id] ? t.tx('发送中…', 'Sending…') : t.tx('发送关怀', 'Send Care')}
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
      </div>
    </div>
  );
}

function DailyCompareChart({ daily, t }) {
  const max = Math.max(1, ...daily.map(d => Math.max(d.deposit, d.payout)));
  return (
    <div className="card">
      <div className="card-h"><h3>{t.tx('本周每日 充值 vs 派彩', 'Daily Deposit vs Payout (this week)')}</h3><span className="meta">{t.tx('最近 7 天', 'Last 7 days')}</span></div>
      <div style={{ padding: 18 }}>
        <div style={{ display: 'grid', gridTemplateColumns: `repeat(${daily.length}, 1fr)`, gap: 14, alignItems: 'end', height: 180 }}>
          {daily.map(d => {
            const depH = (d.deposit / max) * 160;
            const payH = (d.payout / max) * 160;
            return (
              <div key={d.key} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}>
                <div style={{ display: 'flex', gap: 4, alignItems: 'end', height: 160 }}>
                  <div title={t.tx('充值 ', 'Deposit ') + _yuan(d.deposit, t)} style={{ width: 18, height: Math.max(2, depH), background: 'var(--accent)', borderRadius: '3px 3px 0 0' }} />
                  <div title={t.tx('派彩 ', 'Payout ') + _yuan(d.payout, t)} style={{ width: 18, height: Math.max(2, payH), background: 'var(--warning)', borderRadius: '3px 3px 0 0' }} />
                </div>
                <div className="text-muted text-mono" style={{ fontSize: 10, marginTop: 4 }}>{d.label}</div>
              </div>
            );
          })}
        </div>
        <div style={{ display: 'flex', gap: 14, marginTop: 12, fontSize: 11, color: 'var(--text-muted)' }}>
          <span style={{ display: 'flex', alignItems: 'center', gap: 5 }}><span style={{ width: 10, height: 10, background: 'var(--accent)', borderRadius: 2 }} /> {t.tx('充值', 'Deposit')}</span>
          <span style={{ display: 'flex', alignItems: 'center', gap: 5 }}><span style={{ width: 10, height: 10, background: 'var(--warning)', borderRadius: 2 }} /> {t.tx('派彩', 'Payout')}</span>
        </div>
      </div>
    </div>
  );
}

// TODO:
// - 关怀消息真发送：当前 /api/admin/notif/broadcast 是全局广播，未来需要新增 /api/admin/notif/send 支持 target_user_id
// - 走势图换成轻量图表库（chart.js / echarts UMD）以避免大表格性能问题
// - 控盘操作按 role 分级：force-draw 只允许 super-admin，其他允许 admin
Object.assign(window, { PoolControlScreen, TrendDetectionScreen, WinbackScreen });
