// admin.jsx — Admin dashboard, orders/users tables, AI logs, A/B tests, support.
// Wired to /api/admin/* JSON endpoints. UI на русском, design сохранён без изменений.

// ============ helpers ============

const adminFetch = async (path, opts = {}) => {
  const res = await fetch(path, {
    credentials: 'same-origin',
    headers: { 'Content-Type': 'application/json', ...(opts.headers || {}) },
    ...opts,
  });
  if (!res.ok) {
    let msg = `HTTP ${res.status}`;
    try { const j = await res.json(); if (j && j.error) msg = j.error; } catch (_) {}
    throw new Error(msg);
  }
  return res.json();
};

const fmtRub = (v) => {
  if (v == null) return '0 ₽';
  return Math.round(v).toLocaleString('ru') + ' ₽';
};
const fmtNum = (v) => (v == null ? '0' : v.toLocaleString('ru'));
const fmtPct = (v, d = 1) => (v == null ? '0%' : `${Number(v).toFixed(d)}%`);
const fmtDateTime = (iso) => {
  if (!iso) return '—';
  try {
    const d = new Date(iso);
    return d.toLocaleString('ru', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' });
  } catch (_) { return iso; }
};
const fmtRelTime = (iso) => {
  if (!iso) return '—';
  const d = new Date(iso);
  const s = Math.floor((Date.now() - d.getTime()) / 1000);
  if (s < 60) return `${s} сек назад`;
  if (s < 3600) return `${Math.floor(s/60)} мин назад`;
  if (s < 86400) return `${Math.floor(s/3600)} ч назад`;
  return `${Math.floor(s/86400)} дн назад`;
};
const fmtDate = (iso) => {
  if (!iso) return '—';
  const d = new Date(iso);
  return d.toLocaleDateString('ru', { day: '2-digit', month: '2-digit', year: 'numeric' });
};

const PRIO_LABELS = { high: 'высокий', med: 'средний', low: 'низкий' };
const PRIO_COLORS = { high: 'rose', med: 'gold', low: 'celest' };
const ORDER_STATUS_COLORS = {
  pending_payment: 'celest',
  paid: 'gold',
  generating: 'gold',
  ready: 'sage',
  failed: 'rose',
  cancelled: 'rose',
  refunded: 'rose',
};

// ============ shared UI ============

const AdminHeader = ({ subPage, onSubPage, currentUser }) => {
  const items = [
    { id: 'dashboard', label: 'Дашборд' },
    { id: 'orders',    label: 'Заказы' },
    { id: 'products',  label: 'Продукты' },
    { id: 'users',     label: 'Пользователи' },
    { id: 'ai',        label: 'AI / Логи' },
    { id: 'abtests',   label: 'A/B тесты' },
    { id: 'support',   label: 'Поддержка' },
    { id: 'settings',  label: 'Настройки' },
  ];
  const email = currentUser?.email || 'admin@star-path.ru';
  const initial = (email[0] || 'A').toUpperCase();
  return (
    <div className="subnav">
      <div className="row" style={{ gap: '8px', alignItems: 'center', flex: 1 }}>
        <a href="/" title="На главную" style={{ display: 'inline-flex', alignItems: 'center', gap: '8px', textDecoration: 'none', color: 'inherit', marginRight: '12px' }}>
          <span style={{ display: 'inline-flex', color: 'var(--gold)' }}><LogoMark size={20} /></span>
          <span style={{ fontFamily: 'var(--font-display)', fontSize: '16px' }}>Star Path</span>
        </a>
        {items.map(it => (
          <button key={it.id} className={`subnav-link ${subPage === it.id ? 'active' : ''}`} onClick={() => onSubPage(it.id)}>
            {it.label}
          </button>
        ))}
      </div>
      <div className="row gap-3" style={{ alignItems: 'center' }}>
        <span className="mono muted" style={{ fontSize: '11px' }}>{email}</span>
        <div style={{ width: 28, height: 28, borderRadius: '50%', background: 'var(--gold)', color: 'var(--bg-base)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '11px', fontFamily: 'var(--font-mono)' }}>{initial}</div>
        <button className="btn btn-ghost btn-sm" onClick={() => window.__logout && window.__logout()}>Выйти</button>
      </div>
    </div>
  );
};

const LoadingState = ({ text = 'Загрузка…' }) => (
  <div className="card" style={{ padding: '48px', background: 'var(--bg-raised)', textAlign: 'center' }}>
    <span className="dot dot-sage" style={{ animation: 'twinkle 1.6s infinite', display: 'inline-block', marginRight: 8 }} />
    <span className="mono muted" style={{ fontSize: '12px' }}>{text}</span>
  </div>
);

const ErrorState = ({ message, onRetry }) => (
  <div className="card" style={{ padding: '32px', background: 'var(--bg-raised)' }}>
    <div className="label" style={{ color: 'var(--rose)', marginBottom: 12 }}>Ошибка загрузки</div>
    <div className="mono muted" style={{ fontSize: 12, marginBottom: 16 }}>{message}</div>
    {onRetry && <button className="btn btn-ghost btn-sm" onClick={onRetry}>Повторить</button>}
  </div>
);

const EmptyState = ({ text }) => (
  <div className="card" style={{ padding: '48px', background: 'var(--bg-raised)', textAlign: 'center' }}>
    <div className="mono muted" style={{ fontSize: 12 }}>{text || 'Пока нет данных'}</div>
  </div>
);

// ——— Stat tile ———
const StatTile = ({ label, value, unit, delta, sub, sparkline = [] }) => {
  const sparkW = 120, sparkH = 36;
  const arr = (sparkline || []).filter(v => v != null);
  const max = arr.length ? Math.max(...arr, 1) : 1;
  const min = arr.length ? Math.min(...arr, 0) : 0;
  const range = max - min || 1;
  const points = arr.map((v, i) => {
    const x = arr.length > 1 ? (i / (arr.length - 1)) * sparkW : sparkW / 2;
    const y = sparkH - ((v - min) / range) * sparkH;
    return `${x},${y}`;
  }).join(' ');
  const positive = (delta || 0) >= 0;
  return (
    <div className="card" style={{ padding: '24px', background: 'var(--bg-raised)' }}>
      <div className="between" style={{ marginBottom: '20px' }}>
        <span className="label">{label}</span>
        {delta !== undefined && delta !== null && (
          <span className="mono" style={{ fontSize: '11px', color: positive ? 'var(--sage)' : 'var(--rose)' }}>
            {positive ? '↑' : '↓'} {Math.abs(Math.round(delta))}%
          </span>
        )}
      </div>
      <div className="row" style={{ alignItems: 'baseline', gap: '6px', marginBottom: '12px' }}>
        <span className="display-md" style={{ fontSize: '40px', letterSpacing: '-0.025em' }}>{value}</span>
        {unit && <span className="mono muted" style={{ fontSize: '13px' }}>{unit}</span>}
      </div>
      <div className="between" style={{ alignItems: 'flex-end' }}>
        <span className="mono muted" style={{ fontSize: '11px' }}>{sub}</span>
        {arr.length > 0 && (
          <svg width={sparkW} height={sparkH} style={{ overflow: 'visible' }}>
            <polyline points={points} fill="none" stroke={positive ? 'var(--gold)' : 'var(--rose)'} strokeWidth="1.5" />
            <circle cx={arr.length > 1 ? sparkW : sparkW / 2} cy={sparkH - ((arr[arr.length - 1] - min) / range) * sparkH} r="2.5" fill={positive ? 'var(--gold)' : 'var(--rose)'} />
          </svg>
        )}
      </div>
    </div>
  );
};

// ============ DASHBOARD ============

const RevenueChart = ({ daily }) => {
  const data = (daily || []).map(d => d.rub);
  if (!data.length) {
    return (
      <div className="card" style={{ padding: '32px', background: 'var(--bg-raised)' }}>
        <div className="label" style={{ marginBottom: '8px' }}>Выручка / День</div>
        <EmptyState text="Нет оплаченных заказов за 30 дней" />
      </div>
    );
  }
  const max = Math.max(...data, 1);
  const totalRub = data.reduce((a, b) => a + b, 0);
  return (
    <div className="card" style={{ padding: '32px', background: 'var(--bg-raised)' }}>
      <div className="between" style={{ marginBottom: '32px' }}>
        <div>
          <div className="label" style={{ marginBottom: '8px' }}>Выручка за 30 дней</div>
          <div className="row" style={{ alignItems: 'baseline', gap: '12px' }}>
            <span className="display-md" style={{ fontSize: '40px' }}>{fmtRub(totalRub)}</span>
            <span className="mono muted" style={{ fontSize: '12px' }}>{daily.length} дн · {data.reduce((s,_,i) => s + (daily[i]?.orders || 0), 0)} заказов</span>
          </div>
        </div>
      </div>

      <div style={{ position: 'relative', height: '220px' }}>
        {[0, 25, 50, 75, 100].map((y) => (
          <div key={y} style={{ position: 'absolute', left: 0, right: 0, bottom: `${y}%`, borderBottom: '1px dashed var(--line-soft)' }}>
            <span className="mono muted" style={{ position: 'absolute', left: 0, fontSize: '10px', transform: 'translateY(-50%)', background: 'var(--bg-raised)', paddingRight: 6 }}>
              {fmtRub(Math.round(max * y / 100))}
            </span>
          </div>
        ))}
        <svg width="100%" height="100%" viewBox="0 0 100 100" preserveAspectRatio="none" style={{ position: 'absolute', inset: 0 }}>
          <defs>
            <linearGradient id="rev-grad" x1="0" y1="0" x2="0" y2="1">
              <stop offset="0%" stopColor="var(--gold)" stopOpacity="0.4" />
              <stop offset="100%" stopColor="var(--gold)" stopOpacity="0" />
            </linearGradient>
          </defs>
          {data.length > 1 && (
            <>
              <polygon
                points={`0,100 ${data.map((v, i) => `${(i / (data.length - 1)) * 100},${100 - (v / max) * 100}`).join(' ')} 100,100`}
                fill="url(#rev-grad)"
              />
              <polyline
                points={data.map((v, i) => `${(i / (data.length - 1)) * 100},${100 - (v / max) * 100}`).join(' ')}
                fill="none" stroke="var(--gold)" strokeWidth="0.4" vectorEffect="non-scaling-stroke"
              />
            </>
          )}
        </svg>
        <div style={{ position: 'absolute', inset: 0, display: 'flex', alignItems: 'flex-end', gap: '2px', paddingLeft: 40 }}>
          {data.map((v, i) => (
            <div key={i} style={{ flex: 1, height: `${(v / max) * 100}%`, background: 'var(--gold)', opacity: i === data.length - 1 ? 1 : 0.08, borderRadius: '2px 2px 0 0' }} />
          ))}
        </div>
      </div>
    </div>
  );
};

const ProductMix = ({ mix }) => {
  if (!mix || !mix.length) {
    return (
      <div className="card" style={{ padding: '32px', background: 'var(--bg-raised)' }}>
        <div className="label" style={{ marginBottom: '24px' }}>Карта продуктов</div>
        <EmptyState text="Нет данных за 30 дней" />
      </div>
    );
  }
  const palette = ['var(--gold)', 'var(--celest)', 'var(--rose)', 'var(--sage)'];
  const data = mix.map((p, i) => ({
    name: p.service_ru || p.service,
    val: Math.round((p.share || 0) * 100),
    color: palette[i % palette.length],
  }));
  const r = 70, c = 2 * Math.PI * r;
  let acc = 0;
  return (
    <div className="card" style={{ padding: '32px', background: 'var(--bg-raised)' }}>
      <div className="label" style={{ marginBottom: '24px' }}>Карта продуктов</div>
      <div className="row gap-8" style={{ alignItems: 'center' }}>
        <svg width="180" height="180" viewBox="-90 -90 180 180" style={{ transform: 'rotate(-90deg)' }}>
          <circle r={r} fill="none" stroke="var(--line)" strokeWidth="22" />
          {data.map((d, i) => {
            const len = (d.val / 100) * c;
            const segment = (
              <circle key={i} r={r} fill="none" stroke={d.color} strokeWidth="22"
                strokeDasharray={`${len} ${c}`}
                strokeDashoffset={-acc}
              />
            );
            acc += len + 2;
            return segment;
          })}
        </svg>
        <div className="col" style={{ gap: '14px', flex: 1 }}>
          {data.map((d, i) => (
            <div key={i}>
              <div className="between" style={{ marginBottom: '4px' }}>
                <div className="row gap-2" style={{ alignItems: 'center' }}>
                  <span className="dot" style={{ background: d.color, width: 8, height: 8 }} />
                  <span style={{ fontSize: '13px' }}>{d.name}</span>
                </div>
                <span className="mono" style={{ fontSize: '13px' }}>{d.val}%</span>
              </div>
              <div style={{ height: '2px', background: 'var(--line-soft)', borderRadius: 999 }}>
                <div style={{ height: '100%', width: `${d.val}%`, background: d.color, borderRadius: 999 }} />
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

const Funnel = ({ funnel }) => {
  if (!funnel || funnel.visits === 0) {
    return (
      <div className="card" style={{ padding: '32px', background: 'var(--bg-raised)' }}>
        <div className="label" style={{ marginBottom: '12px' }}>Воронка</div>
        <EmptyState text="Пока нет визитов" />
      </div>
    );
  }
  const visits = funnel.unique_visitors || funnel.visits || 1;
  const pct = (n) => Math.min(100, Math.max(0, (n / visits) * 100));
  const data = [
    { label: 'Уникальных визитов', val: funnel.unique_visitors || 0, pct: 100 },
    { label: 'Просмотры заказа', val: funnel.order_views || 0, pct: pct(funnel.order_views || 0) },
    { label: 'Открыли оплату', val: funnel.checkouts || 0, pct: pct(funnel.checkouts || 0) },
    { label: 'Оплатили', val: funnel.paid || 0, pct: pct(funnel.paid || 0) },
  ];
  return (
    <div className="card" style={{ padding: '32px', background: 'var(--bg-raised)' }}>
      <div className="between" style={{ marginBottom: '24px' }}>
        <div className="label">Воронка</div>
        <span className="mono muted" style={{ fontSize: '11px' }}>30 дней · {fmtPct(funnel.conv_vis_to_paid, 2)} конверсия</span>
      </div>
      <div className="col" style={{ gap: '8px' }}>
        {data.map((d, i) => (
          <div key={i} style={{ position: 'relative' }}>
            <div className="between" style={{ marginBottom: '4px' }}>
              <span style={{ fontSize: '13px' }}>{d.label}</span>
              <div className="row gap-3" style={{ alignItems: 'center' }}>
                <span className="mono" style={{ fontSize: '12px' }}>{fmtNum(d.val)}</span>
                <span className="mono muted" style={{ fontSize: '11px', minWidth: 50, textAlign: 'right' }}>{d.pct.toFixed(1)}%</span>
              </div>
            </div>
            <div style={{ height: '24px', background: 'var(--bg-base)', borderRadius: '4px', overflow: 'hidden' }}>
              <div style={{
                height: '100%',
                width: `${d.pct}%`,
                background: `linear-gradient(90deg, var(--gold), color-mix(in oklab, var(--gold) 60%, var(--rose)))`,
                opacity: 1 - i * 0.1,
                transition: 'width 600ms ease',
              }} />
            </div>
          </div>
        ))}
      </div>
    </div>
  );
};

const AdminDashboard = () => {
  const [state, setState] = React.useState({ loading: true, data: null, error: null });

  const load = React.useCallback(async () => {
    setState(s => ({ ...s, loading: true, error: null }));
    try {
      const data = await adminFetch('/api/admin/dashboard');
      setState({ loading: false, data, error: null });
    } catch (e) {
      setState({ loading: false, data: null, error: e.message });
    }
  }, []);
  React.useEffect(() => { load(); }, [load]);

  const nowStr = new Date().toLocaleString('ru', { day: 'numeric', month: 'long', year: 'numeric', hour: '2-digit', minute: '2-digit' });

  if (state.loading && !state.data) {
    return (
      <div className="section" style={{ padding: '32px 56px 80px' }}>
        <div className="eyebrow" style={{ marginBottom: '12px' }}>{nowStr} MSK</div>
        <h1 className="display-lg" style={{ marginBottom: 32 }}>Командный <span className="italic gold">дашборд</span></h1>
        <LoadingState text="Считаем метрики…" />
      </div>
    );
  }
  if (state.error) {
    return (
      <div className="section" style={{ padding: '32px 56px 80px' }}>
        <ErrorState message={state.error} onRetry={load} />
      </div>
    );
  }

  const k = state.data.kpis || {};
  const rev = k.revenue_30d || {};
  const ord = k.orders_30d || {};
  const ltv = k.ltv_avg || {};
  const ref = k.refund_rate || {};
  const errors = state.data.errors || {};
  const funnel = state.data.funnel;
  const support = state.data.support;
  const recent = state.data.recent_orders || [];
  const active = state.data.active_generations || [];

  return (
    <div className="section" style={{ padding: '32px 56px 80px' }} data-screen-label="10 Админ / Дашборд">
      <div className="between" style={{ marginBottom: '32px', alignItems: 'flex-end' }}>
        <div>
          <div className="eyebrow" style={{ marginBottom: '12px' }}>{nowStr} MSK</div>
          <h1 className="display-lg">Командный <span className="italic gold">дашборд</span></h1>
        </div>
        <div className="row gap-3">
          <button className="btn btn-ghost btn-sm" onClick={load}><IconRefresh size={14} /> Обновить</button>
        </div>
      </div>

      {/* Top stats */}
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(5, 1fr)', gap: '16px', marginBottom: '24px' }}>
        <StatTile
          label="Выручка (30 дн)"
          value={fmtRub(rev.current).replace(' ₽', '')}
          unit="₽"
          delta={Math.round(rev.delta_pct || 0)}
          sub={`vs прошлые 30 дн · ${fmtRub(rev.previous)}`}
          sparkline={rev.sparkline}
        />
        <StatTile
          label="Заказов (30 дн)"
          value={fmtNum(Math.round(ord.current || 0))}
          delta={Math.round(ord.delta_pct || 0)}
          sub={`всего юзеров ${fmtNum(k.users_total)}`}
          sparkline={ord.sparkline}
        />
        <StatTile
          label="LTV"
          value={fmtRub(ltv.current).replace(' ₽', '')}
          unit="₽"
          delta={Math.round(ltv.delta_pct || 0)}
          sub="средний за 30 дней"
        />
        <StatTile
          label="Возвраты"
          value={fmtPct(ref.current, 1).replace('%', '')}
          unit="%"
          delta={-Math.round(ref.delta_pct || 0)}
          sub={`failed: ${errors.failed_30d || 0} за 30 дн`}
        />
        <StatTile
          label="Сожжено на ретраях"
          value={`$${(errors.cost_burnt_30d || 0).toFixed(2)}`}
          sub={`упавшие попытки LLM · 24ч $${(errors.cost_burnt_24h || 0).toFixed(2)}`}
        />
      </div>

      {/* Revenue + funnel */}
      <div style={{ display: 'grid', gridTemplateColumns: '1.6fr 1fr', gap: '16px', marginBottom: '24px' }}>
        <RevenueChart daily={state.data.daily_revenue} />
        <Funnel funnel={funnel} />
      </div>

      {/* Product mix + recent orders */}
      <div style={{ display: 'grid', gridTemplateColumns: '1fr 1.6fr', gap: '16px', marginBottom: '24px' }}>
        <ProductMix mix={state.data.product_mix} />
        <div className="card" style={{ padding: '32px', background: 'var(--bg-raised)' }}>
          <div className="between" style={{ marginBottom: '20px' }}>
            <div className="label">Последние заказы</div>
            <span className="mono muted" style={{ fontSize: 11 }}>NPS {support?.nps ? Math.round(support.nps) : '—'} · CSAT {support?.csat_avg ? support.csat_avg.toFixed(1) : '—'}</span>
          </div>
          {recent.length === 0 && <EmptyState text="Заказов пока нет" />}
          {recent.length > 0 && (
            <div className="col" style={{ gap: 8 }}>
              {recent.slice(0, 6).map(o => (
                <div key={o.id} className="between" style={{ padding: '10px 0', borderBottom: '1px solid var(--line-soft)' }}>
                  <div className="row gap-3" style={{ alignItems: 'center' }}>
                    <span className="mono gold" style={{ fontSize: 11 }}>{o.id.slice(0, 8)}</span>
                    <span style={{ fontSize: 13 }}>{o.user_name || o.user_email}</span>
                    <span className="mono muted" style={{ fontSize: 11 }}>{o.service_ru}</span>
                  </div>
                  <div className="row gap-3" style={{ alignItems: 'center' }}>
                    <span className="mono" style={{ fontSize: 12 }}>{fmtRub(o.amount_rub)}</span>
                    <span className="chip" style={{
                      fontSize: 10,
                      color: `var(--${ORDER_STATUS_COLORS[o.status] || 'celest'})`,
                      background: `color-mix(in oklab, var(--${ORDER_STATUS_COLORS[o.status] || 'celest'}) 10%, transparent)`,
                      borderColor: `color-mix(in oklab, var(--${ORDER_STATUS_COLORS[o.status] || 'celest'}) 40%, transparent)`,
                    }}>{o.status_ru}</span>
                  </div>
                </div>
              ))}
            </div>
          )}
        </div>
      </div>

      {/* Live AI status */}
      <div className="card" style={{ padding: '24px', background: 'var(--bg-raised)' }}>
        <div className="between" style={{ marginBottom: '20px' }}>
          <div className="label">AI-генерация · сейчас</div>
          <span className="row gap-2" style={{ alignItems: 'center' }}>
            <span className="dot dot-sage" style={{ animation: 'twinkle 1.6s infinite' }} />
            <span className="mono" style={{ fontSize: '11px', color: 'var(--sage)' }}>{active.length} активны</span>
          </span>
        </div>
        {active.length === 0 && <EmptyState text="Сейчас ничего не генерируется" />}
        {active.length > 0 && (
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px' }}>
            {active.map((j) => (
              <div key={j.id} className="card" style={{ padding: '16px', background: 'var(--bg-base)' }}>
                <div className="between" style={{ marginBottom: '8px' }}>
                  <span className="mono gold" style={{ fontSize: '11px' }}>{j.id.slice(0, 8)}</span>
                  <span className="mono muted" style={{ fontSize: '11px' }}>{fmtRelTime(j.created_at)}</span>
                </div>
                <div style={{ fontSize: '14px', marginBottom: '4px' }}>{j.service_ru}</div>
                <div className="mono muted" style={{ fontSize: '11px', marginBottom: '12px' }}>{j.user_name || j.user_email}</div>
                <div style={{ height: '3px', background: 'var(--line)', borderRadius: 999, overflow: 'hidden' }}>
                  <div style={{ height: '100%', width: j.status === 'generating' ? '60%' : '20%', background: 'var(--gold)' }} />
                </div>
                <div className="mono muted" style={{ fontSize: '10px', marginTop: '6px', textAlign: 'right' }}>{j.status === 'generating' ? 'генерация' : 'в очереди'}</div>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
};

// ============ ORDERS ============

const AdminOrders = () => {
  const [state, setState] = React.useState({ loading: true, orders: [], total: 0, error: null });
  const [q, setQ] = React.useState('');
  const [status, setStatus] = React.useState('');
  const [service, setService] = React.useState('');
  const [page, setPage] = React.useState(1);
  const [actionId, setActionId] = React.useState(null);
  const pageSize = 50;

  const load = React.useCallback(async () => {
    setState(s => ({ ...s, loading: true, error: null }));
    try {
      const params = new URLSearchParams({
        limit: String(pageSize),
        offset: String((page - 1) * pageSize),
      });
      if (q) params.set('q', q);
      if (status) params.set('status', status);
      if (service) params.set('service', service);
      const d = await adminFetch('/api/admin/orders?' + params.toString());
      setState({ loading: false, orders: d.orders || [], total: d.total || 0, error: null });
    } catch (e) {
      setState({ loading: false, orders: [], total: 0, error: e.message });
    }
  }, [q, status, service, page]);

  // debounce search
  React.useEffect(() => {
    const t = setTimeout(load, 250);
    return () => clearTimeout(t);
  }, [load]);

  const action = async (id, endpoint, body) => {
    setActionId(id + endpoint);
    try {
      await adminFetch(`/api/admin/orders/${id}/${endpoint}`, {
        method: 'POST',
        body: body ? JSON.stringify(body) : null,
      });
      await load();
    } catch (e) {
      alert('Ошибка: ' + e.message);
    } finally {
      setActionId(null);
    }
  };

  const onResend = (o) => action(o.id, 'resend');
  const onRequeue = (o) => action(o.id, 'requeue');
  const onRerender = (o) => action(o.id, 'rerender-pdf');
  const onRefund = (o) => {
    const reason = prompt('Причина возврата:');
    if (reason && reason.trim()) action(o.id, 'refund', { reason: reason.trim() });
  };

  const totalPages = Math.max(1, Math.ceil(state.total / pageSize));

  return (
    <div className="section" style={{ padding: '32px 56px 80px' }} data-screen-label="11 Админ / Заказы">
      <div className="between" style={{ marginBottom: '32px', alignItems: 'flex-end' }}>
        <div>
          <div className="eyebrow" style={{ marginBottom: '12px' }}>{fmtNum(state.total)} заказов всего</div>
          <h1 className="display-lg">Заказы</h1>
        </div>
        <div className="row gap-3">
          <button className="btn btn-ghost btn-sm" onClick={load}><IconRefresh size={14} /> Обновить</button>
        </div>
      </div>

      <div className="card" style={{ padding: '16px', marginBottom: '24px', background: 'var(--bg-raised)', display: 'flex', alignItems: 'center', gap: '12px' }}>
        <div style={{ position: 'relative', flex: 1 }}>
          <IconSearch size={14} style={{ position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)', color: 'var(--ink-muted)' }} />
          <input className="input" placeholder="Поиск по email или имени..." value={q} onChange={e => { setQ(e.target.value); setPage(1); }} style={{ paddingLeft: 36 }} />
        </div>
        <select className="select" value={service} onChange={e => { setService(e.target.value); setPage(1); }} style={{ width: 180 }}>
          <option value="">Все продукты</option>
          <option value="natal">Натальная карта</option>
          <option value="year_ahead">Прогноз на год</option>
          <option value="compatibility">Совместимость</option>
          <option value="matrix">Матрица судьбы</option>
          <option value="personal_forecast">Индивидуальный гороскоп</option>
        </select>
        <select className="select" value={status} onChange={e => { setStatus(e.target.value); setPage(1); }} style={{ width: 180 }}>
          <option value="">Любой статус</option>
          <option value="ready">Готов</option>
          <option value="generating">Генерируется</option>
          <option value="paid">Оплачен</option>
          <option value="pending_payment">Ожидает оплаты</option>
          <option value="failed">Ошибка</option>
          <option value="refunded">Возврат</option>
        </select>
      </div>

      {state.error && <ErrorState message={state.error} onRetry={load} />}
      {!state.error && state.loading && state.orders.length === 0 && <LoadingState />}
      {!state.error && !state.loading && state.orders.length === 0 && <EmptyState text="Заказы не найдены" />}
      {state.orders.length > 0 && (
        <div className="card" style={{ padding: 0, background: 'var(--bg-raised)', overflow: 'hidden' }}>
          <div style={{
            display: 'grid',
            gridTemplateColumns: '90px 1.4fr 1.2fr 1fr 1fr 0.7fr 120px 160px',
            padding: '14px 24px',
            borderBottom: '1px solid var(--line)',
            background: 'var(--bg-elevated)',
          }}>
            {['ID', 'Клиент', 'По кому', 'Продукт', 'Дата', 'Сумма', 'Статус', 'Действия'].map((h, i) => (
              <span key={i} className="mono muted" style={{ fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase' }}>{h}</span>
            ))}
          </div>
          {state.orders.map((r, i) => (
            <div key={r.id} style={{
              display: 'grid',
              gridTemplateColumns: '90px 1.4fr 1.2fr 1fr 1fr 0.7fr 120px 160px',
              padding: '16px 24px',
              borderBottom: i < state.orders.length - 1 ? '1px solid var(--line-soft)' : 'none',
              alignItems: 'center',
              transition: 'background 120ms',
            }}>
              <span className="mono gold" style={{ fontSize: '12px' }}>{r.id.slice(0, 8)}</span>
              <div>
                <div style={{ fontSize: '14px' }}>{r.user_name || '—'}</div>
                <div className="mono muted" style={{ fontSize: '11px' }}>{r.user_email}</div>
              </div>
              <span style={{ fontSize: '13px' }} title={r.person_name || ''}>{r.person_name || <span className="muted">—</span>}</span>
              <span style={{ fontSize: '13px' }}>{r.service_ru}</span>
              <span className="mono muted" style={{ fontSize: '12px' }}>{fmtDateTime(r.created_at)}</span>
              <span className="mono" style={{ fontSize: '13px' }}>{fmtRub(r.amount_rub)}</span>
              <span className="chip" style={{
                color: `var(--${ORDER_STATUS_COLORS[r.status] || 'celest'})`,
                background: `color-mix(in oklab, var(--${ORDER_STATUS_COLORS[r.status] || 'celest'}) 10%, transparent)`,
                borderColor: `color-mix(in oklab, var(--${ORDER_STATUS_COLORS[r.status] || 'celest'}) 40%, transparent)`,
                gap: '6px',
                padding: '4px 10px',
                width: 'fit-content',
              }}>
                <span className="dot" style={{ background: `var(--${ORDER_STATUS_COLORS[r.status] || 'celest'})`, width: 5, height: 5 }} />
                {r.status_ru}
              </span>
              <div className="row gap-2">
                {r.status === 'ready' && (
                  <button className="btn btn-ghost btn-sm" disabled={actionId === r.id + 'resend'} onClick={() => onResend(r)} title="Переслать PDF на email">
                    {actionId === r.id + 'resend' ? '…' : 'Отпр.'}
                  </button>
                )}
                {(r.status === 'failed' || r.status === 'ready' || r.status === 'generating') && (
                  <button className="btn btn-ghost btn-sm" disabled={actionId === r.id + 'requeue'} onClick={() => onRequeue(r)} title="Перегенерировать (полная пересборка с LLM)">
                    {actionId === r.id + 'requeue' ? '…' : '↻'}
                  </button>
                )}
                {r.status === 'ready' && (
                  <button className="btn btn-ghost btn-sm" disabled={actionId === r.id + 'rerender-pdf'} onClick={() => onRerender(r)} title="Пересобрать PDF из готовых секций (без LLM)">
                    {actionId === r.id + 'rerender-pdf' ? '…' : 'PDF'}
                  </button>
                )}
                {['paid', 'generating', 'ready', 'failed'].includes(r.status) && (
                  <button className="btn btn-ghost btn-sm" disabled={actionId === r.id + 'refund'} onClick={() => onRefund(r)} title="Возврат">
                    {actionId === r.id + 'refund' ? '…' : 'Возв.'}
                  </button>
                )}
              </div>
            </div>
          ))}
        </div>
      )}

      {state.total > 0 && (
        <div className="between" style={{ marginTop: '20px' }}>
          <span className="mono muted" style={{ fontSize: '11px' }}>
            {(page - 1) * pageSize + 1}–{Math.min(page * pageSize, state.total)} из {fmtNum(state.total)}
          </span>
          <div className="row gap-2">
            <button className="btn btn-icon" disabled={page <= 1} onClick={() => setPage(p => Math.max(1, p - 1))}><IconArrowLeft size={14} /></button>
            <span className="mono muted" style={{ fontSize: 12, padding: '0 12px' }}>{page} / {totalPages}</span>
            <button className="btn btn-icon" disabled={page >= totalPages} onClick={() => setPage(p => Math.min(totalPages, p + 1))}><IconArrowRight size={14} /></button>
          </div>
        </div>
      )}
    </div>
  );
};

// ============ USERS ============

const AdminUsers = () => {
  const [state, setState] = React.useState({ loading: true, users: [], total: 0, stats: {}, error: null });
  const [q, setQ] = React.useState('');
  const [page, setPage] = React.useState(1);
  const [promoting, setPromoting] = React.useState(null);
  const pageSize = 50;

  const load = React.useCallback(async () => {
    setState(s => ({ ...s, loading: true, error: null }));
    try {
      const params = new URLSearchParams({ limit: String(pageSize), offset: String((page - 1) * pageSize) });
      if (q) params.set('q', q);
      const d = await adminFetch('/api/admin/users?' + params.toString());
      setState({ loading: false, users: d.users || [], total: d.total || 0, stats: d.stats || {}, error: null });
    } catch (e) {
      setState({ loading: false, users: [], total: 0, stats: {}, error: e.message });
    }
  }, [q, page]);

  React.useEffect(() => {
    const t = setTimeout(load, 250);
    return () => clearTimeout(t);
  }, [load]);

  const onToggleRole = async (u) => {
    const newRole = u.role === 'admin' ? 'user' : 'admin';
    if (!confirm(`Сменить роль ${u.email} на «${newRole}»?`)) return;
    setPromoting(u.id);
    try {
      await adminFetch(`/api/admin/users/${u.id}/role`, { method: 'POST', body: JSON.stringify({ role: newRole }) });
      await load();
    } catch (e) {
      alert('Ошибка: ' + e.message);
    } finally {
      setPromoting(null);
    }
  };

  const tierColors = { gold: 'gold', silver: 'celest', new: 'sage', refund: 'rose' };
  const tierLabels = { gold: 'Gold (5к+)', silver: 'Silver', new: 'Новый', refund: 'Возврат' };

  const totalPages = Math.max(1, Math.ceil(state.total / pageSize));

  return (
    <div className="section" style={{ padding: '32px 56px 80px' }} data-screen-label="12 Админ / Пользователи">
      <div className="between" style={{ marginBottom: '32px', alignItems: 'flex-end' }}>
        <div>
          <div className="eyebrow" style={{ marginBottom: '12px' }}>{fmtNum(state.total)} пользователей · {fmtNum(state.stats.paying || 0)} платящих</div>
          <h1 className="display-lg">Пользователи</h1>
        </div>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px', marginBottom: '24px' }}>
        <StatTile label="Всего" value={fmtNum(state.total)} sub="всего в БД" />
        <StatTile label="Платящих" value={fmtNum(state.stats.paying || 0)} unit={state.total ? `(${Math.round((state.stats.paying || 0) / state.total * 100)}%)` : ''} sub="хотя бы один paid+" />
        <StatTile label="Повторных" value={fmtNum(state.stats.repeat || 0)} sub="купили 2+ разбора" />
        <StatTile label="Сумма LTV" value={fmtRub(state.stats.sum_ltv || 0).replace(' ₽', '')} unit="₽" sub="сумма по платящим" />
      </div>

      <div className="card" style={{ padding: '16px', marginBottom: '24px', background: 'var(--bg-raised)' }}>
        <div style={{ position: 'relative' }}>
          <IconSearch size={14} style={{ position: 'absolute', left: 12, top: '50%', transform: 'translateY(-50%)', color: 'var(--ink-muted)' }} />
          <input className="input" placeholder="Поиск по email или имени..." value={q} onChange={e => { setQ(e.target.value); setPage(1); }} style={{ paddingLeft: 36 }} />
        </div>
      </div>

      {state.error && <ErrorState message={state.error} onRetry={load} />}
      {!state.error && state.loading && state.users.length === 0 && <LoadingState />}
      {!state.error && !state.loading && state.users.length === 0 && <EmptyState text="Пользователи не найдены" />}
      {state.users.length > 0 && (
        <div className="card" style={{ padding: 0, background: 'var(--bg-raised)', overflow: 'hidden' }}>
          <div style={{
            display: 'grid',
            gridTemplateColumns: '1.6fr 1fr 90px 110px 1fr 120px 100px',
            padding: '14px 24px',
            borderBottom: '1px solid var(--line)',
            background: 'var(--bg-elevated)',
          }}>
            {['Пользователь', 'Регистрация', 'Заказов', 'LTV', 'Активность', 'Сегмент', 'Роль'].map((h, i) => (
              <span key={i} className="mono muted" style={{ fontSize: '11px', letterSpacing: '0.08em', textTransform: 'uppercase' }}>{h}</span>
            ))}
          </div>
          {state.users.map((u, i) => (
            <div key={u.id} style={{
              display: 'grid',
              gridTemplateColumns: '1.6fr 1fr 90px 110px 1fr 120px 100px',
              padding: '16px 24px',
              borderBottom: i < state.users.length - 1 ? '1px solid var(--line-soft)' : 'none',
              alignItems: 'center',
            }}>
              <div className="row gap-3" style={{ alignItems: 'center' }}>
                <div style={{ width: 32, height: 32, borderRadius: '50%', background: 'var(--bg-elevated)', border: '1px solid var(--line)', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '12px', color: 'var(--gold)', fontFamily: 'var(--font-mono)' }}>
                  {(u.name || u.email || '?')[0].toUpperCase()}
                </div>
                <div>
                  <div style={{ fontSize: '14px' }}>{u.name || '—'}</div>
                  <div className="mono muted" style={{ fontSize: '11px' }}>{u.email}</div>
                </div>
              </div>
              <span className="mono muted" style={{ fontSize: '12px' }}>{fmtDate(u.created_at)}</span>
              <span className="mono" style={{ fontSize: '13px' }}>{u.orders_paid || 0}</span>
              <span className="mono gold" style={{ fontSize: '13px' }}>{fmtRub(u.total_spent_rub || 0)}</span>
              <span className="mono muted" style={{ fontSize: '12px' }}>{u.last_order_at ? fmtRelTime(u.last_order_at) : '—'}</span>
              <span className="chip" style={{
                color: `var(--${tierColors[u.tier] || 'celest'})`,
                background: `color-mix(in oklab, var(--${tierColors[u.tier] || 'celest'}) 10%, transparent)`,
                borderColor: `color-mix(in oklab, var(--${tierColors[u.tier] || 'celest'}) 40%, transparent)`,
                fontSize: '10px',
                width: 'fit-content',
              }}>{tierLabels[u.tier] || u.tier}</span>
              <button
                className="btn btn-ghost btn-sm"
                disabled={promoting === u.id}
                onClick={() => onToggleRole(u)}
                title={`Сейчас: ${u.role}`}
                style={{ color: u.role === 'admin' ? 'var(--gold)' : 'var(--ink-muted)' }}
              >
                {promoting === u.id ? '…' : (u.role === 'admin' ? 'admin' : 'user')}
              </button>
            </div>
          ))}
        </div>
      )}

      {state.total > 0 && (
        <div className="between" style={{ marginTop: '20px' }}>
          <span className="mono muted" style={{ fontSize: '11px' }}>
            {(page - 1) * pageSize + 1}–{Math.min(page * pageSize, state.total)} из {fmtNum(state.total)}
          </span>
          <div className="row gap-2">
            <button className="btn btn-icon" disabled={page <= 1} onClick={() => setPage(p => Math.max(1, p - 1))}><IconArrowLeft size={14} /></button>
            <span className="mono muted" style={{ fontSize: 12, padding: '0 12px' }}>{page} / {totalPages}</span>
            <button className="btn btn-icon" disabled={page >= totalPages} onClick={() => setPage(p => Math.min(totalPages, p + 1))}><IconArrowRight size={14} /></button>
          </div>
        </div>
      )}
    </div>
  );
};

// ============ AI LOGS ============

const AdminAI = () => {
  const [state, setState] = React.useState({ loading: true, logs: [], stats: {}, error: null });

  const load = React.useCallback(async () => {
    setState(s => ({ ...s, loading: true, error: null }));
    try {
      const d = await adminFetch('/api/admin/ai?limit=200');
      setState({ loading: false, logs: d.logs || [], stats: d.stats || {}, error: null });
    } catch (e) {
      setState({ loading: false, logs: [], stats: {}, error: e.message });
    }
  }, []);
  React.useEffect(() => { load(); }, [load]);

  const statusColor = { generating: 'gold', ready: 'sage', failed: 'rose', paid: 'gold', pending_payment: 'celest' };
  const s = state.stats || {};

  const fmtDur = (sec) => {
    if (!sec) return '0с';
    if (sec < 60) return `${Math.round(sec)}с`;
    const m = Math.floor(sec / 60);
    const ss = Math.round(sec - m * 60);
    return `${m}:${String(ss).padStart(2, '0')}`;
  };

  return (
    <div className="section" style={{ padding: '32px 56px 80px' }} data-screen-label="13 Админ / AI">
      <div className="between" style={{ marginBottom: '32px', alignItems: 'flex-end' }}>
        <div>
          <div className="eyebrow" style={{ marginBottom: '12px' }}>Swiss Ephemeris + DeepSeek R1</div>
          <h1 className="display-lg">AI / <span className="italic gold">Логи</span></h1>
        </div>
        <button className="btn btn-ghost btn-sm" onClick={load}><IconRefresh size={14} /> Обновить</button>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px', marginBottom: '24px' }}>
        <StatTile label="Среднее время рендера" value={fmtDur(s.avg_duration_s)} sub="PDF готов с момента оплаты" sparkline={s.sparkline} />
        <StatTile label="Токенов / разбор" value={fmtNum(s.avg_tokens)} unit="t" sub="средн. за 30 дней" />
        <StatTile label="Стоимость / разбор" value={(s.avg_cost_rub || 0).toFixed(1)} unit="₽" sub="LLM-расходы" />
        <StatTile label="Успешность" value={(s.success_rate || 0).toFixed(1)} unit="%" sub={`из ${fmtNum(s.total_runs || 0)} запусков`} />
      </div>

      <div className="card" style={{ padding: 0, background: 'var(--bg-raised)' }}>
        <div className="between" style={{ padding: '20px 24px', borderBottom: '1px solid var(--line)' }}>
          <div className="row gap-3" style={{ alignItems: 'center' }}>
            <span className="dot dot-sage" style={{ animation: 'twinkle 1.6s infinite' }} />
            <div className="label">Журнал LLM-вызовов</div>
          </div>
          <span className="mono muted" style={{ fontSize: 11 }}>{state.logs.length} записей</span>
        </div>
        {state.error && <ErrorState message={state.error} onRetry={load} />}
        {!state.error && state.loading && state.logs.length === 0 && <LoadingState />}
        {!state.error && !state.loading && state.logs.length === 0 && <EmptyState text="Пока нет вызовов" />}
        {state.logs.length > 0 && (
          <div style={{ padding: '8px 0', fontFamily: 'var(--font-mono)', fontSize: '12px', maxHeight: '600px', overflow: 'auto' }}>
            {state.logs.map((l, i) => (
              <div key={l.order_id + i} style={{
                padding: '10px 24px',
                borderBottom: '1px solid var(--line-soft)',
                display: 'grid',
                gridTemplateColumns: '110px 90px 1fr 90px 80px 110px',
                gap: '16px',
                alignItems: 'center',
                fontSize: '12px',
              }}>
                <span style={{ color: 'var(--ink-muted)' }}>{fmtDateTime(l.created_at)}</span>
                <span className="gold">{l.order_id.slice(0, 8)}</span>
                <span style={{ color: 'var(--ink-secondary)' }}>{l.stage}</span>
                <span style={{ color: 'var(--ink-muted)' }}>{fmtNum(l.tokens_sum)} t</span>
                <span style={{ color: 'var(--ink-muted)' }}>{l.duration_ms ? Math.round(l.duration_ms / 1000) + 's' : '—'}</span>
                <span style={{
                  color: `var(--${statusColor[l.status] || 'celest'})`,
                  textTransform: 'uppercase',
                  letterSpacing: '0.1em',
                }}>● {l.status}</span>
              </div>
            ))}
          </div>
        )}
      </div>
    </div>
  );
};

// ============ A/B TESTS ============

const AdminABTests = () => {
  const [state, setState] = React.useState({ loading: true, tests: [], error: null });

  const load = React.useCallback(async () => {
    setState(s => ({ ...s, loading: true, error: null }));
    try {
      const d = await adminFetch('/api/admin/abtests');
      setState({ loading: false, tests: d.tests || [], error: null });
    } catch (e) {
      setState({ loading: false, tests: [], error: e.message });
    }
  }, []);
  React.useEffect(() => { load(); }, [load]);

  return (
    <div className="section" style={{ padding: '32px 56px 80px' }} data-screen-label="14 Админ / A/B">
      <div className="between" style={{ marginBottom: '32px', alignItems: 'flex-end' }}>
        <div>
          <div className="eyebrow" style={{ marginBottom: '12px' }}>
            {state.tests.filter(t => t.status === 'running').length} идут · {state.tests.filter(t => t.status !== 'running').length} завершено
          </div>
          <h1 className="display-lg">A/B <span className="italic gold">тесты</span></h1>
        </div>
        <button className="btn btn-ghost btn-sm" onClick={load}><IconRefresh size={14} /> Обновить</button>
      </div>

      {state.error && <ErrorState message={state.error} onRetry={load} />}
      {!state.error && state.loading && <LoadingState />}
      {!state.error && !state.loading && state.tests.length === 0 && <EmptyState text="Эксперименты ещё не созданы — добавьте их через SQL (таблица experiments)" />}

      {state.tests.length > 0 && (
        <div className="col gap-4">
          {state.tests.map((t) => {
            // Объединяем variants meta с stats по key
            const statsByKey = {};
            (t.stats || []).forEach(s => { statsByKey[s.key] = s; });
            const variants = (t.variants || []).length
              ? t.variants.map(v => ({ ...v, ...(statsByKey[v.key] || {}) }))
              : (t.stats || []);
            const winner = t.winner || '';
            const totalUsers = variants.reduce((a, v) => a + (v.users || 0), 0);

            // Find best by conv_pct for visual highlight
            let bestKey = null, bestConv = -1;
            variants.forEach(v => { if ((v.conv_pct || 0) > bestConv) { bestConv = v.conv_pct || 0; bestKey = v.key; } });

            return (
              <div key={t.id} className="card" style={{ padding: '32px', background: 'var(--bg-raised)' }}>
                <div className="between" style={{ marginBottom: '24px' }}>
                  <div>
                    <div className="row gap-3" style={{ alignItems: 'center', marginBottom: '8px' }}>
                      <h2 className="display-md" style={{ fontSize: '24px' }}>{t.name}</h2>
                      <span className="chip" style={{
                        fontSize: '10px',
                        color: t.status === 'running' ? 'var(--gold)' : 'var(--sage)',
                        background: `color-mix(in oklab, var(--${t.status === 'running' ? 'gold' : 'sage'}) 12%, transparent)`,
                        borderColor: `color-mix(in oklab, var(--${t.status === 'running' ? 'gold' : 'sage'}) 50%, transparent)`,
                      }}>
                        <span className="dot" style={{ width: 5, height: 5, background: t.status === 'running' ? 'var(--gold)' : 'var(--sage)' }} />
                        {t.status === 'running' ? 'идёт' : t.status}
                      </span>
                    </div>
                    <span className="mono muted" style={{ fontSize: '11px' }}>
                      key: {t.key} · {t.days || 0} дней · {fmtNum(totalUsers)} пользователей
                    </span>
                    {t.description && <div className="mono muted" style={{ fontSize: '11px', marginTop: 6 }}>{t.description}</div>}
                  </div>
                </div>

                {variants.length === 0 && <EmptyState text="Назначений ещё нет" />}
                {variants.length > 0 && (
                  <div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.min(variants.length, 3)}, 1fr)`, gap: '16px' }}>
                    {variants.map((v, j) => {
                      const isWinner = winner === v.key || (winner === '' && v.key === bestKey && totalUsers > 0);
                      const isControl = j === 0;
                      const baseline = variants[0] ? (variants[0].conv_pct || 0) : 0;
                      const lift = j > 0 && baseline > 0 ? Math.round(((v.conv_pct || 0) - baseline) / baseline * 100) : null;
                      return (
                        <div key={v.key} className="card" style={{
                          padding: '20px',
                          background: 'var(--bg-base)',
                          borderColor: isWinner ? 'var(--gold)' : 'var(--line)',
                          position: 'relative',
                        }}>
                          {isWinner && (
                            <span className="chip chip-gold" style={{ position: 'absolute', top: -10, right: 16, fontSize: '10px' }}>
                              <IconCheck size={10} /> лидер
                            </span>
                          )}
                          <div style={{ fontSize: '14px', marginBottom: '12px' }}>{v.name || v.key}</div>
                          <div className="row" style={{ alignItems: 'baseline', gap: '6px', marginBottom: '12px' }}>
                            <span className="display-md" style={{ fontSize: '36px', color: isWinner ? 'var(--gold)' : 'var(--ink-primary)' }}>{(v.conv_pct || 0).toFixed(2)}</span>
                            <span className="mono muted" style={{ fontSize: '13px' }}>% конверсия</span>
                          </div>
                          <div className="between">
                            <span className="mono muted" style={{ fontSize: '11px' }}>{fmtNum(v.users || 0)} юзеров · {fmtNum(v.purchases || 0)} конв.</span>
                            {lift !== null && (
                              <span className="mono" style={{ fontSize: '12px', color: lift > 0 ? 'var(--sage)' : 'var(--rose)' }}>
                                {lift > 0 ? '+' : ''}{lift}%
                              </span>
                            )}
                            {isControl && <span className="mono muted" style={{ fontSize: '11px' }}>контроль</span>}
                          </div>
                        </div>
                      );
                    })}
                  </div>
                )}
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
};

// ============ SUPPORT ============

const AdminSupport = () => {
  const [state, setState] = React.useState({ loading: true, tickets: [], stats: {}, error: null });
  const [filter, setFilter] = React.useState(''); // '', open, replied, closed
  const [openTicket, setOpenTicket] = React.useState(null);

  const load = React.useCallback(async () => {
    setState(s => ({ ...s, loading: true, error: null }));
    try {
      const params = new URLSearchParams({ limit: '100' });
      if (filter) params.set('status', filter);
      const d = await adminFetch('/api/admin/support?' + params.toString());
      setState({ loading: false, tickets: d.tickets || [], stats: d.stats || {}, error: null });
    } catch (e) {
      setState({ loading: false, tickets: [], stats: {}, error: e.message });
    }
  }, [filter]);
  React.useEffect(() => { load(); }, [load]);

  const s = state.stats || {};

  return (
    <div className="section" style={{ padding: '32px 56px 80px' }} data-screen-label="15 Админ / Поддержка">
      <div className="between" style={{ marginBottom: '32px', alignItems: 'flex-end' }}>
        <div>
          <div className="eyebrow" style={{ marginBottom: '12px' }}>{s.open_count || 0} открытых · {s.high_open || 0} высокого приоритета</div>
          <h1 className="display-lg">Поддержка</h1>
        </div>
        <button className="btn btn-ghost btn-sm" onClick={load}><IconRefresh size={14} /> Обновить</button>
      </div>

      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: '16px', marginBottom: '24px' }}>
        <StatTile label="Открытых тикетов" value={fmtNum(s.open_count || 0)} sub={`высокий приоритет: ${s.high_open || 0}`} />
        <StatTile label="Среднее время ответа" value={s.avg_response_s ? Math.round((s.avg_response_s || 0) / 60) : '—'} unit="мин" sub="по тикетам за 30 дней" />
        <StatTile label="NPS" value={s.nps != null ? Math.round(s.nps) : '—'} sub={`за 90 дней · ${s.feedback_count || 0} отзывов`} />
        <StatTile label="CSAT" value={s.csat_avg ? s.csat_avg.toFixed(1) : '—'} unit="/ 5" sub="средняя оценка" />
      </div>

      {openTicket ? (
        <TicketDetailPanel ticketId={openTicket} onClose={() => { setOpenTicket(null); load(); }} />
      ) : (
        <div className="card" style={{ padding: 0, background: 'var(--bg-raised)' }}>
          <div className="between" style={{ padding: '20px 24px', borderBottom: '1px solid var(--line)' }}>
            <div className="label">Тикеты</div>
            <div className="row gap-2">
              {[
                { id: '', label: 'Все' },
                { id: 'open', label: 'Открытые' },
                { id: 'replied', label: 'Отвечены' },
                { id: 'closed', label: 'Закрытые' },
              ].map(opt => (
                <button key={opt.id} className="chip" onClick={() => setFilter(opt.id)} style={{
                  cursor: 'pointer',
                  background: filter === opt.id ? 'var(--ink-primary)' : 'var(--bg-elevated)',
                  color: filter === opt.id ? 'var(--bg-base)' : 'var(--ink-secondary)',
                  borderColor: filter === opt.id ? 'var(--ink-primary)' : 'var(--line)',
                }}>{opt.label}</button>
              ))}
            </div>
          </div>
          {state.error && <ErrorState message={state.error} onRetry={load} />}
          {!state.error && state.loading && state.tickets.length === 0 && <LoadingState />}
          {!state.error && !state.loading && state.tickets.length === 0 && <EmptyState text="Тикеты не найдены" />}
          {state.tickets.map((t, i) => (
            <div key={t.id} onClick={() => setOpenTicket(t.id)} style={{
              display: 'grid',
              gridTemplateColumns: '90px 90px 1.4fr 1.8fr 90px 90px',
              gap: '16px',
              padding: '18px 24px',
              borderBottom: i < state.tickets.length - 1 ? '1px solid var(--line-soft)' : 'none',
              alignItems: 'center',
              cursor: 'pointer',
            }}>
              <span className="mono gold" style={{ fontSize: '12px' }}>{t.id.slice(0, 8)}</span>
              <span className="chip" style={{
                color: `var(--${PRIO_COLORS[t.priority] || 'celest'})`,
                background: `color-mix(in oklab, var(--${PRIO_COLORS[t.priority] || 'celest'}) 12%, transparent)`,
                borderColor: `color-mix(in oklab, var(--${PRIO_COLORS[t.priority] || 'celest'}) 50%, transparent)`,
                fontSize: '10px',
                width: 'fit-content',
              }}>
                {PRIO_LABELS[t.priority] || t.priority}
              </span>
              <div>
                <div style={{ fontSize: '13px' }}>{t.user_name || '—'}</div>
                <div className="mono muted" style={{ fontSize: '11px' }}>{t.user_email}</div>
              </div>
              <span style={{ fontSize: '14px', color: 'var(--ink-secondary)' }}>{t.subject}</span>
              <span className="mono muted" style={{ fontSize: '11px' }}>{fmtRelTime(t.updated_at)}</span>
              <span className="mono" style={{ fontSize: '11px', color: t.status === 'open' ? 'var(--rose)' : t.status === 'replied' ? 'var(--gold)' : 'var(--ink-muted)' }}>
                {t.status === 'open' ? 'открыт' : t.status === 'replied' ? 'отвечен' : 'закрыт'}
              </span>
            </div>
          ))}
        </div>
      )}
    </div>
  );
};

const TicketDetailPanel = ({ ticketId, onClose }) => {
  const [state, setState] = React.useState({ loading: true, ticket: null, messages: [], error: null });
  const [reply, setReply] = React.useState('');
  const [sending, setSending] = React.useState(false);

  const load = React.useCallback(async () => {
    setState(s => ({ ...s, loading: true, error: null }));
    try {
      const d = await adminFetch(`/api/admin/support/${ticketId}`);
      setState({ loading: false, ticket: d.ticket, messages: d.messages || [], error: null });
    } catch (e) {
      setState({ loading: false, ticket: null, messages: [], error: e.message });
    }
  }, [ticketId]);
  React.useEffect(() => { load(); }, [load]);

  const sendReply = async () => {
    const body = reply.trim();
    if (!body) return;
    setSending(true);
    try {
      await adminFetch(`/api/admin/support/${ticketId}/reply`, { method: 'POST', body: JSON.stringify({ body }) });
      setReply('');
      await load();
    } catch (e) {
      alert('Ошибка: ' + e.message);
    } finally {
      setSending(false);
    }
  };

  const setStatus = async (status) => {
    try {
      await adminFetch(`/api/admin/support/${ticketId}/status`, { method: 'POST', body: JSON.stringify({ status }) });
      await load();
    } catch (e) {
      alert('Ошибка: ' + e.message);
    }
  };

  if (state.loading) return <LoadingState text="Открываем тикет…" />;
  if (state.error) return <ErrorState message={state.error} onRetry={load} />;
  const t = state.ticket;
  if (!t) return null;

  return (
    <div className="card" style={{ padding: 0, background: 'var(--bg-raised)' }}>
      <div className="between" style={{ padding: '20px 24px', borderBottom: '1px solid var(--line)' }}>
        <div>
          <div className="row gap-3" style={{ alignItems: 'center' }}>
            <span className="mono gold" style={{ fontSize: 12 }}>{t.id.slice(0, 8)}</span>
            <span className="chip" style={{
              color: `var(--${PRIO_COLORS[t.priority] || 'celest'})`,
              background: `color-mix(in oklab, var(--${PRIO_COLORS[t.priority] || 'celest'}) 12%, transparent)`,
              borderColor: `color-mix(in oklab, var(--${PRIO_COLORS[t.priority] || 'celest'}) 50%, transparent)`,
              fontSize: 10,
            }}>{PRIO_LABELS[t.priority] || t.priority}</span>
            <span className="mono muted" style={{ fontSize: 11 }}>{t.user_email}</span>
          </div>
          <div style={{ fontSize: 18, marginTop: 8 }}>{t.subject}</div>
        </div>
        <div className="row gap-2">
          <button className="btn btn-ghost btn-sm" onClick={onClose}>← Назад к списку</button>
        </div>
      </div>

      <div style={{ padding: '20px 24px', display: 'flex', flexDirection: 'column', gap: 12, maxHeight: 500, overflow: 'auto' }}>
        {state.messages.map(m => (
          <div key={m.id} style={{
            alignSelf: m.author === 'admin' ? 'flex-end' : 'flex-start',
            maxWidth: '75%',
            padding: '12px 16px',
            background: m.author === 'admin' ? 'color-mix(in oklab, var(--gold) 18%, var(--bg-base))' : 'var(--bg-base)',
            borderRadius: 8,
            border: '1px solid var(--line-soft)',
          }}>
            <div className="row gap-2" style={{ marginBottom: 4 }}>
              <span className="mono muted" style={{ fontSize: 10 }}>{m.author === 'admin' ? 'Админ' : m.author === 'user' ? 'Клиент' : 'Система'}</span>
              <span className="mono muted" style={{ fontSize: 10 }}>{fmtDateTime(m.created_at)}</span>
            </div>
            <div style={{ fontSize: 14, whiteSpace: 'pre-wrap' }}>{m.body}</div>
          </div>
        ))}
        {state.messages.length === 0 && <EmptyState text="Сообщений пока нет" />}
      </div>

      <div style={{ padding: '20px 24px', borderTop: '1px solid var(--line)', display: 'flex', flexDirection: 'column', gap: 12 }}>
        <textarea
          className="input"
          value={reply}
          onChange={e => setReply(e.target.value)}
          placeholder="Ваш ответ клиенту…"
          rows={4}
          style={{ resize: 'vertical', minHeight: 90 }}
        />
        <div className="between">
          <div className="row gap-2">
            {t.status !== 'closed' && <button className="btn btn-ghost btn-sm" onClick={() => setStatus('closed')}>Закрыть тикет</button>}
            {t.status === 'closed' && <button className="btn btn-ghost btn-sm" onClick={() => setStatus('open')}>Открыть заново</button>}
          </div>
          <button className="btn btn-gold" disabled={sending || !reply.trim()} onClick={sendReply}>
            {sending ? 'Отправляем…' : 'Отправить ответ'}
          </button>
        </div>
      </div>
    </div>
  );
};

// ============ PRODUCTS ============

const AdminProducts = () => {
  const [rows, setRows] = React.useState(null);
  const [err, setErr] = React.useState(null);
  const [savingKey, setSavingKey] = React.useState(null);
  const [savedKey, setSavedKey] = React.useState(null);

  const load = () => {
    adminFetch('/api/admin/products')
      .then(d => setRows(d.products || []))
      .catch(e => setErr(e.message));
  };
  React.useEffect(load, []);

  const patch = (key, field, value) => {
    setRows(rows.map(r => r.key === key ? { ...r, [field]: value } : r));
  };

  const save = async (row) => {
    setSavingKey(row.key); setErr(null);
    try {
      const d = await adminFetch(`/api/admin/products/${row.key}`, {
        method: 'PUT',
        body: JSON.stringify({ price_rub: Number(row.price_rub), enabled: !!row.enabled, title: row.title }),
      });
      setRows(d.products || rows);
      setSavedKey(row.key); setTimeout(() => setSavedKey(null), 1800);
    } catch (e) { setErr(e.message); } finally { setSavingKey(null); }
  };

  if (err && !rows) return <div className="card" style={{ padding: 24, color: 'var(--rose)' }}>Ошибка: {err}</div>;
  if (!rows) return <LoadingState />;

  return (
    <div className="card" style={{ padding: '28px', background: 'var(--bg-raised)' }}>
      <div className="between" style={{ marginBottom: 20, alignItems: 'flex-end' }}>
        <div>
          <div className="eyebrow" style={{ marginBottom: 8 }}>Каталог</div>
          <h2 className="display" style={{ fontSize: 26 }}>Продукты и цены</h2>
        </div>
        <span className="mono muted" style={{ fontSize: 11 }}>Цены тянутся отсюда: каталог, заказы, лендинги</span>
      </div>
      {err && <div className="mono" style={{ color: 'var(--rose)', fontSize: 12, marginBottom: 12 }}>{err}</div>}
      <table className="dt" style={{ width: '100%' }}>
        <thead><tr>
          <th>Ключ</th><th>Название</th><th style={{ width: 130 }}>Цена, ₽</th>
          <th style={{ width: 90 }}>В каталоге</th><th style={{ width: 110 }}></th>
        </tr></thead>
        <tbody>
          {rows.map(row => (
            <tr key={row.key}>
              <td><span className="mono muted" style={{ fontSize: 11 }}>{row.key}</span></td>
              <td><input className="input" value={row.title} onChange={e => patch(row.key, 'title', e.target.value)} style={{ minWidth: 200 }} /></td>
              <td><input className="input mono" type="number" min="0" value={row.price_rub} onChange={e => patch(row.key, 'price_rub', e.target.value)} style={{ width: 110 }} /></td>
              <td style={{ textAlign: 'center' }}><input type="checkbox" checked={!!row.enabled} onChange={e => patch(row.key, 'enabled', e.target.checked)} style={{ cursor: 'pointer' }} /></td>
              <td>
                <button className="btn btn-sm btn-primary" disabled={savingKey === row.key} onClick={() => save(row)}>
                  {savingKey === row.key ? '…' : savedKey === row.key ? 'OK ✓' : 'Сохранить'}
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
};

// ============ SETTINGS ============

const AdminSettings = () => {
  const [rows, setRows] = React.useState(null);
  const [err, setErr] = React.useState(null);
  const [savingKey, setSavingKey] = React.useState(null);
  const [savedKey, setSavedKey] = React.useState(null);

  const load = () => {
    adminFetch('/api/admin/settings')
      .then(d => setRows(d.settings || []))
      .catch(e => setErr(e.message));
  };
  React.useEffect(load, []);

  const patch = (key, value) => setRows(rows.map(r => r.key === key ? { ...r, value } : r));

  const save = async (row) => {
    setSavingKey(row.key); setErr(null);
    try {
      const d = await adminFetch(`/api/admin/settings/${row.key}`, {
        method: 'PUT', body: JSON.stringify({ value: row.value }),
      });
      setRows(d.settings || rows);
      setSavedKey(row.key); setTimeout(() => setSavedKey(null), 1800);
    } catch (e) { setErr(e.message); } finally { setSavingKey(null); }
  };

  if (err && !rows) return <div className="card" style={{ padding: 24, color: 'var(--rose)' }}>Ошибка: {err}</div>;
  if (!rows) return <LoadingState />;

  return (
    <div className="card" style={{ padding: '28px', background: 'var(--bg-raised)', maxWidth: 720 }}>
      <div style={{ marginBottom: 20 }}>
        <div className="eyebrow" style={{ marginBottom: 8 }}>Реквизиты и контакты</div>
        <h2 className="display" style={{ fontSize: 26 }}>Настройки сайта</h2>
        <p className="mono muted" style={{ fontSize: 11, marginTop: 6 }}>Расползаются по сайту: футеры, страница заказа, оферта, политика.</p>
      </div>
      {err && <div className="mono" style={{ color: 'var(--rose)', fontSize: 12, marginBottom: 12 }}>{err}</div>}
      <div className="col gap-4">
        {rows.map(row => (
          <div key={row.key} className="row gap-3" style={{ alignItems: 'flex-end' }}>
            <label className="field" style={{ flex: 1 }}>
              <span className="label">{row.label} <span className="mono muted" style={{ fontSize: 10 }}>{row.key}</span></span>
              <input className="input" value={row.value} onChange={e => patch(row.key, e.target.value)} />
            </label>
            <button className="btn btn-sm btn-primary" disabled={savingKey === row.key} onClick={() => save(row)}>
              {savingKey === row.key ? '…' : savedKey === row.key ? 'OK ✓' : 'Сохранить'}
            </button>
          </div>
        ))}
      </div>
    </div>
  );
};

// ============ ROOT ============

const Admin = ({ subPage = 'dashboard', onSubPage }) => {
  const [me, setMe] = React.useState(null);
  React.useEffect(() => {
    fetch('/api/auth/me', { credentials: 'same-origin' })
      .then(r => r.ok ? r.json() : null)
      .then(d => { if (d && d.email) setMe(d); })
      .catch(() => {});
  }, []);
  return (
    <>
      <AdminHeader subPage={subPage} onSubPage={onSubPage} currentUser={me} />
      {subPage === 'dashboard' && <AdminDashboard />}
      {subPage === 'orders' && <AdminOrders />}
      {subPage === 'products' && <AdminProducts />}
      {subPage === 'users' && <AdminUsers />}
      {subPage === 'ai' && <AdminAI />}
      {subPage === 'abtests' && <AdminABTests />}
      {subPage === 'support' && <AdminSupport />}
      {subPage === 'settings' && <AdminSettings />}
    </>
  );
};

Object.assign(window, { Admin });
