// =======================================================================
// MariusVPN Admin — Shared UI primitives (cards, buttons, inputs,
// toasts, modal, table, charts, icons).
// =======================================================================

const { useState, useEffect, useRef, useMemo, useCallback, createContext, useContext } = React;

// --------------- Icons (Lucide-style inline SVG) -------------------------

const Icon = ({ d, size = 18, stroke = 'currentColor', sw = 1.8, fill = 'none', className = '' }) => (
  <svg width={size} height={size} viewBox="0 0 24 24" fill={fill} stroke={stroke} strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" className={className}>
    {Array.isArray(d) ? d.map((p, i) => <path key={i} d={p} />) : <path d={d} />}
  </svg>
);

const ICONS = {
  dashboard:   'M3 13h8V3H3v10zm0 8h8v-6H3v6zm10 0h8V11h-8v10zm0-18v6h8V3h-8z',
  users:       ['M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2', 'M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z', 'M22 21v-2a4 4 0 0 0-3-3.87', 'M17 3.13a4 4 0 0 1 0 7.75'],
  finance:     ['M12 1v22', 'M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6'],
  server:      ['M2 3h20v6H2zM2 15h20v6H2z', 'M6 6h.01', 'M6 18h.01'],
  traffic:     ['M3 17l6-6 4 4 8-8', 'M14 7h7v7'],
  hwid:        ['M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z', 'M3.27 6.96L12 12.01l8.73-5.05', 'M12 22.08V12'],
  broadcast:   ['M3 11l18-8-3 18-6-7-9-3z'],
  franchise:   ['M3 21V7l9-4 9 4v14', 'M9 22V12h6v10'],
  settings:    ['M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z','M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z'],
  search:      ['M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z', 'M21 21l-4.35-4.35'],
  plus:        'M12 5v14M5 12h14',
  minus:       'M5 12h14',
  close:       'M18 6L6 18M6 6l12 12',
  check:       'M20 6L9 17l-5-5',
  chevR:       'M9 18l6-6-6-6',
  chevL:       'M15 18l-6-6 6-6',
  chevD:       'M6 9l6 6 6-6',
  logout:      ['M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4', 'M16 17l5-5-5-5', 'M21 12H9'],
  warn:        ['M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z', 'M12 9v4', 'M12 17h.01'],
  ban:         ['M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z', 'M4.93 4.93l14.14 14.14'],
  edit:        ['M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7', 'M18.5 2.5a2.12 2.12 0 1 1 3 3L12 15l-4 1 1-4 9.5-9.5z'],
  trash:       ['M3 6h18', 'M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6', 'M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2'],
  msg:         ['M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'],
  gift:        ['M20 12v10H4V12','M2 7h20v5H2z','M12 22V7','M12 7H7.5a2.5 2.5 0 1 1 0-5C11 2 12 7 12 7z','M12 7h4.5a2.5 2.5 0 1 0 0-5C13 2 12 7 12 7z'],
  refresh:     ['M23 4v6h-6', 'M1 20v-6h6', 'M3.51 9a9 9 0 0 1 14.85-3.36L23 10', 'M1 14l4.64 4.36A9 9 0 0 0 20.49 15'],
  shield:      ['M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z'],
  bell:        ['M18 8a6 6 0 1 0-12 0c0 7-3 9-3 9h18s-3-2-3-9', 'M13.73 21a2 2 0 0 1-3.46 0'],
  send:        ['M22 2L11 13', 'M22 2l-7 20-4-9-9-4 20-7z'],
  link:        ['M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71', 'M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71'],
  download:    ['M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4', 'M7 10l5 5 5-5', 'M12 15V3'],
  filter:      ['M22 3H2l8 9.46V19l4 2v-8.54L22 3z'],
  calendar:    ['M19 4H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2z', 'M16 2v4', 'M8 2v4', 'M3 10h18'],
  copy:        ['M20 9h-9a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2z', 'M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1'],
  globe:       ['M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10z','M2 12h20','M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z'],
  wallet:      ['M21 12V7H5a2 2 0 0 1 0-4h14v4', 'M3 5v14a2 2 0 0 0 2 2h16v-5', 'M18 12a2 2 0 0 0 0 4h4v-4z'],
  trending:    ['M23 6l-9.5 9.5-5-5L1 18', 'M17 6h6v6'],
  picture:     ['M21 19V5a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2z','M8.5 10a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3z','M21 15l-5-5L5 21'],
  sparkles:    ['M12 3l1.91 5.92L20 11l-6.09 1.91L12 19l-1.91-6.09L4 11l5.91-2.08L12 3z'],
  zap:         ['M13 2L3 14h9l-1 8 10-12h-9l1-8z'],
};

// --------------- Toasts ---------------------------------------------------

const ToastCtx = createContext(null);
function ToastProvider({ children }) {
  const [list, setList] = useState([]);
  const idRef = useRef(0);
  const push = useCallback((msg, kind = 'ok') => {
    const id = ++idRef.current;
    setList(l => [...l, { id, msg, kind }]);
    setTimeout(() => setList(l => l.filter(t => t.id !== id)), 3200);
  }, []);
  const api = useMemo(() => ({
    ok:   (m) => push(m, 'ok'),
    err:  (m) => push(m, 'err'),
    info: (m) => push(m, 'info'),
  }), [push]);
  return (
    <ToastCtx.Provider value={api}>
      {children}
      <div className="fixed bottom-6 right-6 z-[100] flex flex-col gap-2 items-end pointer-events-none">
        {list.map(t => (
          <div key={t.id} className={
            'toast-in pointer-events-auto rounded-xl px-4 py-3 shadow-lift backdrop-blur min-w-[260px] max-w-[420px] text-sm border ' +
            (t.kind === 'err'  ? 'bg-m-err/15 border-m-err/30 text-[#FCA5A5]'
            : t.kind === 'info' ? 'bg-m-accent/10 border-m-accent/30 text-m-accent'
            : 'bg-m-ok/15 border-m-ok/30 text-[#86EFAC]')
          }>
            <div className="flex items-start gap-2.5">
              <div className="mt-0.5 shrink-0">
                {t.kind === 'err' ? <Icon d={ICONS.warn} size={16}/> : t.kind === 'info' ? <Icon d={ICONS.bell} size={16}/> : <Icon d={ICONS.check} size={16}/>}
              </div>
              <div className="flex-1">{t.msg}</div>
            </div>
          </div>
        ))}
      </div>
    </ToastCtx.Provider>
  );
}
const useToast = () => useContext(ToastCtx);

// --------------- Card / Section ------------------------------------------

const Card = ({ className = '', children, padding = 'p-5', ...rest }) => (
  <div {...rest} className={`bg-m-card border border-m-line rounded-2xl shadow-card ${padding} ${className}`}>{children}</div>
);

const SectionHeader = ({ title, subtitle, right }) => (
  <div className="flex items-end justify-between mb-5 gap-4 flex-wrap">
    <div>
      <h1 className="text-[26px] font-bold tracking-tight text-m-text">{title}</h1>
      {subtitle && <p className="text-m-mute text-sm mt-1">{subtitle}</p>}
    </div>
    {right && <div className="flex items-center gap-2">{right}</div>}
  </div>
);

// --------------- Buttons -------------------------------------------------

const Button = ({ variant = 'primary', size = 'md', icon, children, className = '', loading, disabled, ...rest }) => {
  const base = 'inline-flex items-center justify-center gap-2 font-medium rounded-xl transition active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed select-none';
  const sizes = { sm: 'h-8 px-3 text-[13px]', md: 'h-10 px-4 text-sm', lg: 'h-12 px-5 text-[15px]' };
  const variants = {
    primary:   'bg-m-accent text-[#062029] hover:bg-cyan-300',
    secondary: 'bg-white/[0.04] hover:bg-white/[0.08] text-m-text border border-m-line',
    ghost:     'text-m-mute hover:text-m-text hover:bg-white/[0.04]',
    danger:    'bg-m-err/15 text-[#FCA5A5] hover:bg-m-err/25 border border-m-err/30',
    success:   'bg-m-ok/15 text-[#86EFAC] hover:bg-m-ok/25 border border-m-ok/30',
    gold:      'bg-m-gold/15 text-m-gold hover:bg-m-gold/25 border border-m-gold/30',
  };
  return (
    <button {...rest} disabled={disabled || loading} className={`${base} ${sizes[size]} ${variants[variant]} ${className}`}>
      {loading ? <Icon d="M21 12a9 9 0 1 1-6.219-8.56" className="spin" size={16}/> : icon}
      {children}
    </button>
  );
};

const IconButton = ({ icon, onClick, title, variant = 'ghost', className = '' }) => {
  const variants = {
    ghost:  'text-m-mute hover:text-m-text hover:bg-white/[0.06]',
    danger: 'text-m-err/80 hover:text-m-err hover:bg-m-err/10',
  };
  return (
    <button title={title} onClick={onClick} className={`h-9 w-9 rounded-lg inline-flex items-center justify-center transition ${variants[variant]} ${className}`}>
      {React.isValidElement(icon) ? icon : <Icon d={icon} size={16}/>}
    </button>
  );
};

// --------------- Inputs --------------------------------------------------

const fieldCls = 'w-full h-10 px-3.5 rounded-xl bg-m-bg2 border border-m-line text-m-text placeholder:text-m-dim text-sm focus:outline-none focus:border-m-accent/50 focus:bg-m-bg2 transition';

const Field = ({ label, hint, error, children }) => (
  <label className="block">
    {label && <span className="block text-[12px] font-medium text-m-mute mb-1.5 tracking-wide uppercase">{label}</span>}
    {children}
    {hint && !error && <span className="block text-xs text-m-dim mt-1.5">{hint}</span>}
    {error && <span className="block text-xs text-m-err mt-1.5">{error}</span>}
  </label>
);

const Input = React.forwardRef(({ icon, className = '', ...rest }, ref) => (
  <div className="relative">
    {icon && <span className="absolute left-3 top-1/2 -translate-y-1/2 text-m-dim pointer-events-none"><Icon d={icon} size={16}/></span>}
    <input ref={ref} {...rest} className={`${fieldCls} ${icon ? 'pl-9' : ''} ${className}`} />
  </div>
));

const Textarea = ({ rows = 4, className = '', ...rest }) => (
  <textarea rows={rows} {...rest} className={`${fieldCls} h-auto py-2.5 resize-y leading-relaxed ${className}`} />
);

const Select = ({ children, className = '', ...rest }) => (
  <div className="relative">
    <select {...rest} className={`${fieldCls} appearance-none pr-9 ${className}`}>
      {children}
    </select>
    <span className="absolute right-3 top-1/2 -translate-y-1/2 text-m-dim pointer-events-none"><Icon d={ICONS.chevD} size={14}/></span>
  </div>
);

const Switch = ({ checked, onChange, label, disabled }) => (
  <button
    type="button"
    onClick={() => !disabled && onChange(!checked)}
    disabled={disabled}
    className={`group inline-flex items-center gap-3 disabled:opacity-50 ${label ? 'cursor-pointer' : ''}`}
  >
    <span className={`relative inline-block h-6 w-11 rounded-full transition ${checked ? 'bg-m-accent' : 'bg-white/10'}`}>
      <span className={`absolute top-0.5 left-0.5 h-5 w-5 rounded-full bg-white shadow transition-transform ${checked ? 'translate-x-5' : ''}`} />
    </span>
    {label && <span className="text-sm text-m-text">{label}</span>}
  </button>
);

// --------------- Badges --------------------------------------------------

const Badge = ({ tone = 'mute', children, dot = false, className = '' }) => {
  const tones = {
    ok:    'bg-m-ok/10 text-m-ok border-m-ok/20',
    err:   'bg-m-err/10 text-m-err border-m-err/20',
    warn:  'bg-m-warn/10 text-m-warn border-m-warn/20',
    info:  'bg-m-accent/10 text-m-accent border-m-accent/20',
    mute:  'bg-white/[0.04] text-m-mute border-m-line',
    gold:  'bg-m-gold/10 text-m-gold border-m-gold/20',
  };
  return (
    <span className={`inline-flex items-center gap-1.5 text-[11px] font-medium px-2 py-1 rounded-md border ${tones[tone]} ${className}`}>
      {dot && <span className={`h-1.5 w-1.5 rounded-full ${tone === 'ok' ? 'bg-m-ok pulse-dot' : tone === 'err' ? 'bg-m-err' : tone === 'warn' ? 'bg-m-warn' : tone === 'info' ? 'bg-m-accent' : 'bg-m-mute'}`} />}
      {children}
    </span>
  );
};

// --------------- Modal ---------------------------------------------------

function Modal({ open, onClose, title, children, footer, width = 'max-w-lg' }) {
  useEffect(() => {
    if (!open) return;
    const h = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [open, onClose]);
  if (!open) return null;
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center p-4 fade-in">
      <div className="absolute inset-0 bg-black/60 backdrop-blur-sm" onClick={onClose}/>
      <div className={`relative bg-m-card border border-m-line rounded-2xl shadow-lift w-full ${width} max-h-[90vh] flex flex-col`}>
        {title && (
          <div className="flex items-center justify-between px-6 py-4 border-b border-m-line">
            <h3 className="text-base font-semibold">{title}</h3>
            <IconButton icon={ICONS.close} onClick={onClose}/>
          </div>
        )}
        <div className="px-6 py-5 overflow-y-auto flex-1">{children}</div>
        {footer && <div className="px-6 py-4 border-t border-m-line flex items-center justify-end gap-2">{footer}</div>}
      </div>
    </div>
  );
}

// --------------- Side panel (right drawer) -------------------------------

function SidePanel({ open, onClose, title, children, width = 'w-[640px]' }) {
  useEffect(() => {
    if (!open) return;
    const h = (e) => { if (e.key === 'Escape') onClose(); };
    window.addEventListener('keydown', h);
    return () => window.removeEventListener('keydown', h);
  }, [open, onClose]);
  if (!open) return null;
  return (
    <div className="fixed inset-0 z-50 flex justify-end">
      <div className="absolute inset-0 bg-black/55 backdrop-blur-sm" onClick={onClose}/>
      <div className={`relative slide-right bg-m-bg2 border-l border-m-line ${width} max-w-[100vw] h-full overflow-y-auto`}>
        <div className="sticky top-0 z-10 flex items-center justify-between px-6 py-4 bg-m-bg2/95 backdrop-blur border-b border-m-line">
          <h3 className="text-base font-semibold">{title}</h3>
          <IconButton icon={ICONS.close} onClick={onClose}/>
        </div>
        <div className="p-6">{children}</div>
      </div>
    </div>
  );
}

// --------------- Empty / Spinner / Skeleton ------------------------------

const Spinner = ({ size = 18 }) => <Icon d="M21 12a9 9 0 1 1-6.219-8.56" className="spin text-m-accent" size={size}/>;

const Loading = ({ label = 'Загрузка…' }) => (
  <div className="flex items-center justify-center gap-3 py-16 text-m-mute text-sm">
    <Spinner/> {label}
  </div>
);

const Skeleton = ({ className = '', h = 16 }) => <div className={`skeleton rounded-lg ${className}`} style={{ height: h }}/>;

const Empty = ({ title = 'Пусто', subtitle, icon = ICONS.search }) => (
  <div className="flex flex-col items-center justify-center py-16 px-6 text-center">
    <div className="h-14 w-14 rounded-2xl bg-white/[0.04] border border-m-line flex items-center justify-center text-m-dim mb-3">
      <Icon d={icon} size={22}/>
    </div>
    <div className="text-m-text font-medium">{title}</div>
    {subtitle && <div className="text-m-mute text-sm mt-1 max-w-xs">{subtitle}</div>}
  </div>
);

// --------------- Charts (SVG) --------------------------------------------

function LineChart({ data, valueKey = 'amount', height = 200, color = '#22D3EE', formatY = (v) => v, formatX = (v) => v.slice(5) }) {
  const w = 720, padL = 44, padR = 12, padT = 14, padB = 28;
  if (!data || !data.length) return <div className="text-m-dim text-sm">Нет данных</div>;
  const vals = data.map(d => d[valueKey]);
  const maxV = Math.max(...vals, 1);
  const minV = 0;
  const stepX = (w - padL - padR) / Math.max(1, data.length - 1);
  const yScale = (v) => padT + (height - padT - padB) * (1 - (v - minV) / (maxV - minV));
  const points = data.map((d, i) => [padL + i * stepX, yScale(d[valueKey])]);
  const pathLine = points.map((p, i) => `${i === 0 ? 'M' : 'L'}${p[0].toFixed(1)},${p[1].toFixed(1)}`).join(' ');
  const pathArea = `${pathLine} L${points.at(-1)[0]},${height - padB} L${points[0][0]},${height - padB} Z`;
  const yTicks = 4;
  const ticks = Array.from({ length: yTicks + 1 }, (_, i) => Math.round(maxV * i / yTicks));
  // x labels every Nth
  const xEvery = Math.max(1, Math.ceil(data.length / 7));
  const [hover, setHover] = useState(null);

  const onMove = (e) => {
    const svg = e.currentTarget;
    const rect = svg.getBoundingClientRect();
    const x = (e.clientX - rect.left) * (w / rect.width);
    const idx = Math.max(0, Math.min(data.length - 1, Math.round((x - padL) / stepX)));
    setHover(idx);
  };

  return (
    <div className="w-full">
      <svg viewBox={`0 0 ${w} ${height}`} className="w-full h-auto" onMouseMove={onMove} onMouseLeave={() => setHover(null)}>
        <defs>
          <linearGradient id={`grad-${color.replace('#','')}`} x1="0" x2="0" y1="0" y2="1">
            <stop offset="0%"  stopColor={color} stopOpacity="0.30"/>
            <stop offset="100%" stopColor={color} stopOpacity="0"/>
          </linearGradient>
        </defs>
        {ticks.map((t, i) => (
          <g key={i}>
            <line x1={padL} x2={w - padR} y1={yScale(t)} y2={yScale(t)} stroke="rgba(255,255,255,0.05)" strokeDasharray="3 4"/>
            <text x={padL - 8} y={yScale(t) + 4} fill="#64748B" fontSize="10" textAnchor="end" fontFamily="JetBrains Mono">{formatY(t)}</text>
          </g>
        ))}
        <path d={pathArea} fill={`url(#grad-${color.replace('#','')})`} />
        <path d={pathLine} fill="none" stroke={color} strokeWidth="2"/>
        {data.map((d, i) => i % xEvery === 0 && (
          <text key={i} x={padL + i * stepX} y={height - 8} fill="#64748B" fontSize="10" textAnchor="middle">{formatX(d.date)}</text>
        ))}
        {hover != null && (
          <g>
            <line x1={points[hover][0]} x2={points[hover][0]} y1={padT} y2={height - padB} stroke="rgba(34,211,238,0.4)" strokeDasharray="3 3"/>
            <circle cx={points[hover][0]} cy={points[hover][1]} r="5" fill={color} stroke="#0B0E14" strokeWidth="2"/>
            <g transform={`translate(${Math.min(points[hover][0] + 8, w - 140)}, ${Math.max(points[hover][1] - 28, padT)})`}>
              <rect width="132" height="44" rx="8" fill="#0E1219" stroke="rgba(255,255,255,0.1)"/>
              <text x="10" y="18" fill="#94A3B8" fontSize="11">{data[hover].date}</text>
              <text x="10" y="34" fill="#E5E7EB" fontSize="12" fontWeight="600" fontFamily="JetBrains Mono">{formatY(data[hover][valueKey])}</text>
            </g>
          </g>
        )}
      </svg>
    </div>
  );
}

function BarChart({ data, valueKey = 'count', height = 200, color = '#34D399', formatY = (v) => v }) {
  const w = 720, padL = 36, padR = 12, padT = 14, padB = 28;
  if (!data || !data.length) return <div className="text-m-dim text-sm">Нет данных</div>;
  const vals = data.map(d => d[valueKey]);
  const maxV = Math.max(...vals, 1);
  const stepX = (w - padL - padR) / data.length;
  const bw = Math.max(2, stepX * 0.7);
  const yScale = (v) => padT + (height - padT - padB) * (1 - v / maxV);
  const yTicks = 4;
  const ticks = Array.from({ length: yTicks + 1 }, (_, i) => Math.round(maxV * i / yTicks));
  const xEvery = Math.max(1, Math.ceil(data.length / 7));
  return (
    <svg viewBox={`0 0 ${w} ${height}`} className="w-full h-auto">
      {ticks.map((t, i) => (
        <g key={i}>
          <line x1={padL} x2={w - padR} y1={yScale(t)} y2={yScale(t)} stroke="rgba(255,255,255,0.05)" strokeDasharray="3 4"/>
          <text x={padL - 8} y={yScale(t) + 4} fill="#64748B" fontSize="10" textAnchor="end" fontFamily="JetBrains Mono">{formatY(t)}</text>
        </g>
      ))}
      {data.map((d, i) => {
        const x = padL + i * stepX + (stepX - bw) / 2;
        const y = yScale(d[valueKey]);
        const h = (height - padB) - y;
        return <rect key={i} x={x} y={y} width={bw} height={h} rx="3" fill={color} fillOpacity="0.85"/>;
      })}
      {data.map((d, i) => i % xEvery === 0 && (
        <text key={i} x={padL + i * stepX + stepX/2} y={height - 8} fill="#64748B" fontSize="10" textAnchor="middle">{d.date.slice(5)}</text>
      ))}
    </svg>
  );
}

// --------------- KPI tile ------------------------------------------------

function Kpi({ label, value, hint, accent, icon, hintTone = 'mute' }) {
  const hintTones = { up: 'text-m-ok', down: 'text-m-err', mute: 'text-m-mute' };
  return (
    <Card padding="p-5" className="relative overflow-hidden">
      <div className="flex items-start justify-between mb-3">
        <span className="text-[11px] font-medium uppercase tracking-wider text-m-mute">{label}</span>
        {icon && (
          <span className={`h-8 w-8 rounded-lg flex items-center justify-center ${accent === 'gold' ? 'bg-m-gold/12 text-m-gold' : accent === 'ok' ? 'bg-m-ok/12 text-m-ok' : accent === 'err' ? 'bg-m-err/12 text-m-err' : 'bg-m-accent/12 text-m-accent'}`}>
            <Icon d={icon} size={15}/>
          </span>
        )}
      </div>
      <div className="text-[26px] font-bold tracking-tight text-m-text leading-none">{value}</div>
      {hint && <div className={`text-xs mt-2 ${hintTones[hintTone]}`}>{hint}</div>}
    </Card>
  );
}

// --------------- Confirm dialog ------------------------------------------

function ConfirmDialog({ open, title, message, confirmLabel = 'Подтвердить', danger, onConfirm, onClose, loading }) {
  return (
    <Modal open={open} onClose={onClose} title={title} width="max-w-md"
      footer={
        <>
          <Button variant="secondary" onClick={onClose}>Отмена</Button>
          <Button variant={danger ? 'danger' : 'primary'} onClick={onConfirm} loading={loading}>{confirmLabel}</Button>
        </>
      }
    >
      <p className="text-m-mute text-sm leading-relaxed">{message}</p>
    </Modal>
  );
}

// --------------- Export to globals ---------------------------------------

Object.assign(window, {
  Icon, ICONS,
  ToastProvider, useToast,
  Card, SectionHeader,
  Button, IconButton,
  Field, Input, Textarea, Select, Switch,
  Badge, Modal, SidePanel, ConfirmDialog,
  Spinner, Loading, Skeleton, Empty,
  LineChart, BarChart, Kpi,
});
