// ─────────────────────────────────────────────────────────────────────────
// ДИЗАЙН-КИТ ТАБЛИЦ — дополнительные элементы (публичная ДС).
// Выбор строк и массовые действия, действия в строке, значения в ячейках,
// состояния таблицы (загрузка/ошибка/раскрытие) и НАБОР ФИЛЬТРОВ
// (дропдаун, радио, переключатель, чек-лист, период, активные фильтры).
// Всё на токенах публичной ДС и на иконках window.Icon.
// ─────────────────────────────────────────────────────────────────────────
const Icon = window.Icon;

// keyframes для скелетона (добавляются один раз)
if (typeof document !== 'undefined' && !document.getElementById('tk-extras-style')) {
  const st = document.createElement('style');
  st.id = 'tk-extras-style';
  st.textContent = '@keyframes tkpulse{0%,100%{opacity:.55}50%{opacity:1}}.tk-sk{animation:tkpulse 1.4s ease-in-out infinite;background:var(--ip-surface-4);border-radius:6px}';
  document.head.appendChild(st);
}

/* ═══════════ ВЫБОР СТРОК + МАССОВЫЕ ДЕЙСТВИЯ ═══════════ */
function Checkbox({ checked, indeterminate, onChange, size = 20 }) {
  const on = checked || indeterminate;
  return (
    <button type="button" role="checkbox" aria-checked={indeterminate ? 'mixed' : !!checked} onClick={() => onChange && onChange(!checked)} style={{
      all: 'unset', cursor: 'pointer', width: size, height: size, borderRadius: 6, boxSizing: 'border-box',
      display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0,
      background: on ? 'var(--accent)' : '#fff', boxShadow: on ? 'none' : 'inset 0 0 0 2px var(--border-strong)',
    }}>
      {indeterminate ? <span style={{ width: 10, height: 2, background: '#fff', borderRadius: 1 }} /> : checked ? <Icon name="check" size={14} color="#fff" /> : null}
    </button>
  );
}

function BulkBar({ count, children, onClear }) {
  return (
    <div style={{ display: 'flex', alignItems: 'center', gap: 16, padding: '12px 20px', borderRadius: 'var(--radius-md)', background: 'var(--ip-brand-green-100)', flexWrap: 'wrap' }}>
      <span style={{ fontSize: 15, fontWeight: 600, color: 'var(--ip-brand-green-active)' }}>Выбрано: <b>{count}</b></span>
      <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>{children}</div>
      {onClear && <button type="button" onClick={onClear} style={{ marginLeft: 'auto', all: 'unset', cursor: 'pointer', fontSize: 14, fontWeight: 600, color: 'var(--accent)' }}>Снять выделение</button>}
    </div>
  );
}

/* ═══════════ ДЕЙСТВИЯ В СТРОКЕ ═══════════ */
/* Система кнопок: 3 цвета (green / white / gray) × 2 размера (lg / sm) × с иконкой или без.
   Отступ иконки от текста — 8px во всех вариантах. Legacy-пропсы variant/tone маппятся сюда. */
function Button({ color, variant, tone, size = 'lg', icon, children, onClick, full = false, disabled = false, title }) {
  const [hov, setHov] = React.useState(false);
  const resolved = color
    || (tone === 'neutral' ? 'gray' : tone === 'danger' ? 'danger'
      : (variant === 'outline' || variant === 'outlined') ? 'white' : 'green');
  const sm = size === 'sm' || size === 'small';
  const skin = {
    green: { bg: 'var(--accent)', hov: 'var(--accent-hover)', fg: '#fff', ring: null },
    white: { bg: '#fff', hov: 'var(--ip-brand-green-100)', fg: 'var(--accent)', ring: 'inset 0 0 0 1.5px var(--accent)' },
    gray: { bg: 'var(--bg-soft)', hov: 'var(--border-soft)', fg: 'var(--fg-2)', ring: 'inset 0 0 0 1px var(--border-soft)' },
    danger: { bg: '#fff', hov: '#FDE9E7', fg: 'var(--ip-danger)', ring: 'inset 0 0 0 1.5px var(--ip-danger)' },
  }[resolved];
  const on = hov && !disabled;
  const style = {
    display: full ? 'flex' : 'inline-flex', width: full ? '100%' : undefined,
    alignItems: 'center', justifyContent: 'center', gap: 8,
    height: sm ? 38 : 48, padding: sm ? '0 14px' : '0 20px',
    borderRadius: 'var(--radius-lg)', fontFamily: 'var(--font-sans)', fontSize: sm ? 14 : 15, fontWeight: 600,
    cursor: disabled ? 'default' : 'pointer', border: 'none', whiteSpace: 'nowrap', lineHeight: 1, boxSizing: 'border-box',
    transition: 'background .15s, box-shadow .15s',
    background: disabled ? 'var(--border-strong)' : (on ? skin.hov : skin.bg), color: disabled ? '#fff' : skin.fg,
    boxShadow: disabled ? 'none' : (skin.ring || 'none'),
  };
  return (
    <button type="button" title={title} disabled={disabled} onClick={onClick} style={style}
      onMouseEnter={() => setHov(true)} onMouseLeave={() => setHov(false)}>
      {icon && <Icon name={icon} size={sm ? 16 : 18} color={disabled ? '#fff' : skin.fg} />}
      {children}
    </button>
  );
}

function IconButton({ icon, title, onClick, tone, disabled = false }) {
  const color = disabled ? 'var(--ip-muted-3)' : tone === 'accent' ? 'var(--accent)' : tone === 'danger' ? 'var(--ip-danger)' : 'var(--fg-3)';
  return (
    <button type="button" title={title} disabled={disabled} onClick={disabled ? undefined : onClick} style={{ width: 38, height: 38, borderRadius: 'var(--radius-sm)', border: 'none', cursor: disabled ? 'not-allowed' : 'pointer', background: 'transparent', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}
      onMouseEnter={(e) => !disabled && (e.currentTarget.style.background = 'var(--bg-soft)')}
      onMouseLeave={(e) => !disabled && (e.currentTarget.style.background = 'transparent')}>
      <Icon name={icon} size={20} color={color} />
    </button>
  );
}

function RowMenu({ items = [] }) {
  const [open, setOpen] = React.useState(false);
  return (
    <div style={{ position: 'relative' }}>
      <IconButton icon="dots" title="Ещё" onClick={() => setOpen((o) => !o)} />
      {open && (
        <React.Fragment>
          <div onClick={() => setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 40 }} />
          <div style={{ position: 'absolute', top: 42, right: 0, zIndex: 41, minWidth: 200, padding: '6px 0', background: '#fff', borderRadius: 'var(--radius-sm)', boxShadow: 'var(--shadow-popover)' }}>
            {items.map((it) => (
              <div key={it.label} onClick={() => { it.onClick && it.onClick(); setOpen(false); }} style={{ display: 'flex', alignItems: 'center', gap: 10, padding: '10px 16px', cursor: 'pointer', fontSize: 14.5, color: it.danger ? 'var(--ip-danger)' : 'var(--fg-1)', whiteSpace: 'nowrap' }}
                onMouseEnter={(e) => (e.currentTarget.style.background = 'var(--bg-app)')}
                onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}>
                {it.icon && <Icon name={it.icon} size={18} color={it.danger ? 'var(--ip-danger)' : 'var(--fg-3)'} />}{it.label}
              </div>
            ))}
          </div>
        </React.Fragment>
      )}
    </div>
  );
}

/* ═══════════ ЗНАЧЕНИЯ В ЯЧЕЙКАХ ═══════════ */
function Money({ amount, status, note }) {
  return (
    <div>
      <div style={{ fontSize: 16, fontWeight: 500, fontFamily: 'var(--font-display)', color: 'var(--fg-1)' }}>{amount}</div>
      {status && <div style={{ marginTop: 5 }}><window.Status tone={status.tone}>{status.label}</window.Status></div>}
      {note && <div style={{ fontSize: 12, color: 'var(--fg-muted)', marginTop: 4 }}>{note}</div>}
    </div>
  );
}

function Progress({ done, total, unit = 'ч.' }) {
  const pct = total ? Math.min(100, Math.round(done / total * 100)) : 0;
  return (
    <div style={{ width: 76 }}>
      <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'center', gap: 4 }}>
        <span style={{ fontSize: 16, fontWeight: 600, color: 'var(--accent)' }}>{done}</span>
        <span style={{ fontSize: 13, color: 'var(--fg-3)' }}>/ {total} {unit}</span>
      </div>
      <div style={{ marginTop: 6, height: 5, borderRadius: 999, background: 'var(--ip-surface-4)', overflow: 'hidden' }}><div style={{ width: pct + '%', height: '100%', borderRadius: 999, background: 'var(--accent)' }} /></div>
    </div>
  );
}

function Chips({ items = [] }) {
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', gap: 6 }}>
      {items.map((t) => <span key={t} style={{ display: 'inline-flex', alignItems: 'center', height: 26, padding: '0 11px', borderRadius: 'var(--radius-sm)', background: 'var(--bg-soft)', color: 'var(--fg-2)', fontSize: 13, fontWeight: 600 }}>{t}</span>)}
    </div>
  );
}

function Contact({ phone, email, tg }) {
  const row = (icon, value, href) => value ? (
    <a className="tk-link" href={href}>
      <Icon name={icon} size={16} color="var(--accent)" />{value}
    </a>
  ) : null;
  return (
    <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
      {row('phone', phone, 'tel:' + phone)}
      {row('mail', email, 'mailto:' + email)}
      {row('telegram', tg, '#')}
    </div>
  );
}

function Stepper({ steps = [] }) {
  const cur = steps.find((s) => s.state === 'current');
  return (
    <div>
      <div style={{ display: 'flex', alignItems: 'center' }}>
        {steps.map((s, i) => {
          const done = s.state === 'done';
          const current = s.state === 'current';
          const dotBg = done ? 'var(--accent)' : '#fff';
          const ring = current ? 'var(--accent)' : 'var(--border-strong)';
          return (
            <React.Fragment key={i}>
              {i > 0 && <span style={{ flex: 1, height: 2, minWidth: 14, background: (done || current) ? 'var(--accent)' : 'var(--ip-surface-4)' }} />}
              <span title={s.label} style={{ width: 22, height: 22, flexShrink: 0, borderRadius: '50%', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: dotBg, boxShadow: done ? 'none' : `inset 0 0 0 2px ${ring}` }}>
                {done ? <Icon name="check" size={13} color="#fff" /> : current ? <span style={{ width: 8, height: 8, borderRadius: '50%', background: 'var(--accent)' }} /> : null}
              </span>
            </React.Fragment>
          );
        })}
      </div>
      {cur && <div style={{ fontSize: 13, color: 'var(--fg-3)', marginTop: 8 }}>{cur.label}</div>}
    </div>
  );
}

/* ═══════════ СОСТОЯНИЯ ТАБЛИЦЫ ═══════════ */
function Skeleton({ rows = 3, cols = 4 }) {
  return (
    <div style={{ background: 'var(--bg-card)', borderRadius: 'var(--radius-md)', boxShadow: 'var(--shadow-card)', overflow: 'hidden' }}>
      <div style={{ display: 'flex', gap: 24, padding: '15px 28px', background: 'var(--bg-app)', borderBottom: '1px solid var(--border-soft)' }}>
        {Array.from({ length: cols }).map((_, i) => <span key={i} className="tk-sk" style={{ height: 12, width: i === 1 ? 140 : 80 }} />)}
      </div>
      {Array.from({ length: rows }).map((_, r) => (
        <div key={r} style={{ display: 'flex', gap: 24, alignItems: 'center', padding: '18px 28px', borderBottom: r === rows - 1 ? 'none' : '1px solid var(--border-soft)' }}>
          <span className="tk-sk" style={{ height: 14, width: 60 }} />
          <div style={{ display: 'flex', alignItems: 'center', gap: 10, width: 160 }}><span className="tk-sk" style={{ width: 32, height: 32, borderRadius: '50%' }} /><span className="tk-sk" style={{ height: 12, flex: 1 }} /></div>
          <span className="tk-sk" style={{ height: 12, width: 90 }} />
          <span className="tk-sk" style={{ height: 12, width: 110 }} />
        </div>
      ))}
    </div>
  );
}

function ErrorState({ onRetry }) {
  return (
    <div style={{ background: 'var(--bg-card)', borderRadius: 'var(--radius-md)', boxShadow: 'var(--shadow-card)', padding: '72px 28px', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 14, textAlign: 'center' }}>
      <span style={{ width: 72, height: 72, borderRadius: '50%', background: 'var(--bg-soft)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}><Icon name="alert" size={32} color="var(--ip-muted)" /></span>
      <div style={{ fontSize: 18, fontWeight: 700, color: 'var(--fg-1)' }}>Не удалось загрузить данные</div>
      <div style={{ fontSize: 15, color: 'var(--fg-muted)', maxWidth: 380 }}>Проверьте соединение и попробуйте ещё раз.</div>
      {onRetry && (
        <button type="button" onClick={onRetry} style={{ marginTop: 6, height: 44, padding: '0 20px', borderRadius: 'var(--radius-lg)', border: 'none', cursor: 'pointer', background: '#fff', color: 'var(--accent)', boxShadow: 'inset 0 0 0 1.5px var(--accent)', fontFamily: 'var(--font-sans)', fontSize: 15, fontWeight: 600 }}>Повторить</button>
      )}
    </div>
  );
}

// раскрывающаяся строка (строка → детали)
function Disclosure({ summary, children, defaultOpen }) {
  const [open, setOpen] = React.useState(!!defaultOpen);
  return (
    <div style={{ borderRadius: 'var(--radius-md)', boxShadow: 'inset 0 0 0 1px var(--border-soft)', overflow: 'hidden' }}>
      <button type="button" onClick={() => setOpen((o) => !o)} style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 12, padding: '14px 18px', border: 'none', cursor: 'pointer', background: open ? 'var(--ip-brand-green-100)' : '#fff', fontFamily: 'var(--font-sans)', textAlign: 'left' }}>
        <span style={{ flex: 1, minWidth: 0 }}>{summary}</span>
        <span style={{ display: 'inline-flex', alignItems: 'center', gap: 6, fontSize: 14, fontWeight: 600, color: 'var(--accent)' }}>{open ? 'Свернуть' : 'Подробнее'}<Icon name="chevronDown" size={18} color="var(--accent)" style={{ transform: open ? 'rotate(180deg)' : 'none' }} /></span>
      </button>
      {open && <div style={{ padding: '16px 18px', borderTop: '1px solid var(--border-soft)', background: '#fff' }}>{children}</div>}
    </div>
  );
}

/* ═══════════ ФИЛЬТРЫ ═══════════ */
// Выпадающий фильтр (single-select) с выбранным значением в чипе
function FilterDropdown({ label, value, options = [], onChange, placeholder = 'Любой', multi = false }) {
  const [open, setOpen] = React.useState(false);
  const arr = multi ? (Array.isArray(value) ? value : (value ? [value] : [])) : [];
  const active = multi ? arr.length > 0 : (value != null && value !== '');
  const current = multi ? null : options.find((o) => o.value === value);
  const selChips = multi ? options.filter((o) => arr.includes(o.value)) : [];
  const toggleMulti = (v) => { const next = arr.includes(v) ? arr.filter((x) => x !== v) : [...arr, v]; onChange && onChange(next); };
  return (
    <div style={{ position: 'relative' }}>
      <button type="button" onClick={() => setOpen((o) => !o)} style={{
        display: 'inline-flex', alignItems: 'center', gap: 10, height: 48, padding: '0 12px 0 18px', cursor: 'pointer', background: '#fff',
        borderRadius: 'var(--radius-lg)', border: 'none', boxShadow: `inset 0 0 0 ${active ? 1.5 : 1}px ${active ? 'var(--accent)' : 'var(--border-strong)'}`,
        fontFamily: 'var(--font-sans)', fontSize: 15, fontWeight: 600, color: active ? 'var(--accent)' : 'var(--fg-1)', whiteSpace: 'nowrap',
      }}>
        <span style={{ flexShrink: 0 }}>{label}</span>
        {multi ? (
          arr.length ? <span style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', minWidth: 24, height: 24, padding: '0 8px', borderRadius: 'var(--radius-sm)', background: 'var(--accent)', color: '#fff', fontSize: 13, fontWeight: 700 }}>{arr.length}</span> : <span style={{ color: 'var(--ip-muted)', fontWeight: 500 }}>{placeholder}</span>
        ) : (
          current ? <span style={{ display: 'inline-flex', alignItems: 'center', height: 24, padding: '0 10px', borderRadius: 'var(--radius-sm)', background: 'var(--ip-brand-green-100)', color: 'var(--ip-brand-green-active)', fontSize: 13 }}>{current.label}</span> : <span style={{ color: 'var(--ip-muted)', fontWeight: 500 }}>{placeholder}</span>
        )}
        <Icon name="chevronDown" size={20} color={active ? 'var(--accent)' : 'var(--fg-3)'} style={{ flexShrink: 0, transform: open ? 'rotate(180deg)' : 'none' }} />
      </button>
      {open && (
        <React.Fragment>
          <div onClick={() => setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 40 }} />
          <div style={{ position: 'absolute', top: 'calc(100% + 6px)', left: 0, zIndex: 41, minWidth: 260, padding: '6px 0', background: '#fff', borderRadius: 'var(--radius-sm)', boxShadow: 'var(--shadow-popover)' }}>
            {multi ? (
              <React.Fragment>
                {arr.length > 0 && <div onClick={() => onChange && onChange([])} style={{ display: 'flex', alignItems: 'center', gap: 8, padding: '9px 16px', cursor: 'pointer', fontSize: 13.5, fontWeight: 600, color: 'var(--fg-3)', borderBottom: '1px solid var(--border-soft)' }}><Icon name="x" size={15} color="var(--fg-3)" />Сбросить ({arr.length})</div>}
                {options.map((o) => {
                  const sel = arr.includes(o.value);
                  return (
                    <div key={o.value} onClick={() => toggleMulti(o.value)} style={{ display: 'flex', alignItems: 'center', gap: 12, padding: '10px 16px', cursor: 'pointer', fontSize: 14.5, fontWeight: sel ? 600 : 400, color: 'var(--fg-1)', background: sel ? 'var(--ip-brand-green-100)' : 'transparent' }}
                      onMouseEnter={(e) => { if (!sel) e.currentTarget.style.background = 'var(--bg-app)'; }}
                      onMouseLeave={(e) => { if (!sel) e.currentTarget.style.background = 'transparent'; }}>
                      <span style={{ width: 18, height: 18, flexShrink: 0, borderRadius: 5, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', background: sel ? 'var(--accent)' : '#fff', boxShadow: sel ? 'none' : 'inset 0 0 0 1.5px var(--border-strong)' }}>{sel && <Icon name="check" size={13} color="#fff" />}</span>
                      {o.label}
                    </div>
                  );
                })}
              </React.Fragment>
            ) : (
              [{ value: '', label: placeholder }, ...options].map((o) => {
                const sel = o.value === (value == null ? '' : value);
                return (
                  <div key={o.value} onClick={() => { onChange && onChange(sel ? '' : o.value); setOpen(false); }} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12, padding: '10px 16px', cursor: 'pointer', fontSize: 14.5, fontWeight: sel ? 700 : 400, color: sel ? 'var(--accent)' : 'var(--fg-1)', background: sel ? 'var(--ip-brand-green-100)' : 'transparent' }}
                    onMouseEnter={(e) => { if (!sel) e.currentTarget.style.background = 'var(--bg-app)'; }}
                    onMouseLeave={(e) => { if (!sel) e.currentTarget.style.background = 'transparent'; }}>
                    {o.label}{sel && <Icon name="check" size={17} color="var(--accent)" />}
                  </div>
                );
              })
            )}
          </div>
        </React.Fragment>
      )}
    </div>
  );
}

// Переключатель (switch)
function Switch({ checked, onChange, label }) {
  return (
    <button type="button" onClick={() => onChange(!checked)} style={{ all: 'unset', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 10 }}>
      <span style={{ position: 'relative', width: 38, height: 22, borderRadius: 999, background: checked ? 'var(--accent)' : 'var(--ip-muted-3)', transition: 'background .15s', flexShrink: 0 }}>
        <span style={{ position: 'absolute', top: 2, left: checked ? 18 : 2, width: 18, height: 18, borderRadius: '50%', background: '#fff', transition: 'left .15s', boxShadow: '0 1px 3px rgba(0,0,0,.2)' }} />
      </span>
      {label && <span style={{ fontSize: 14.5, color: 'var(--fg-2)' }}>{label}</span>}
    </button>
  );
}

// Радио-группа
function RadioGroup({ options = [], value, onChange, inline }) {
  return (
    <div style={{ display: 'flex', flexDirection: inline ? 'row' : 'column', gap: inline ? 20 : 12, flexWrap: 'wrap' }}>
      {options.map((o) => {
        const sel = value === o.value;
        return (
          <button key={o.value} type="button" onClick={() => onChange(o.value)} style={{ all: 'unset', cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 10 }}>
            <span style={{ width: 20, height: 20, borderRadius: '50%', flexShrink: 0, boxSizing: 'border-box', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', boxShadow: `inset 0 0 0 2px ${sel ? 'var(--accent)' : 'var(--border-strong)'}` }}>
              {sel && <span style={{ width: 10, height: 10, borderRadius: '50%', background: 'var(--accent)' }} />}
            </span>
            <span style={{ fontSize: 15, color: 'var(--fg-1)' }}>{o.label}</span>
          </button>
        );
      })}
    </div>
  );
}

// Чек-лист (множественный выбор)
function CheckList({ options = [], value = [], onChange, inline }) {
  const toggle = (v) => onChange(value.includes(v) ? value.filter((x) => x !== v) : [...value, v]);
  return (
    <div style={{ display: 'flex', flexDirection: inline ? 'row' : 'column', gap: inline ? 18 : 12, flexWrap: 'wrap' }}>
      {options.map((o) => (
        <span key={o.value} role="checkbox" aria-checked={value.includes(o.value)} onClick={() => toggle(o.value)} style={{ cursor: 'pointer', display: 'inline-flex', alignItems: 'center', gap: 10 }}>
          <Checkbox checked={value.includes(o.value)} onChange={() => toggle(o.value)} />
          <span style={{ fontSize: 15, color: 'var(--fg-1)' }}>{o.label}</span>
        </span>
      ))}
    </div>
  );
}

// ── Календарь (выбор даты) ──
const TK_WD = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
const TK_MONTHS = ['Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь', 'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'];
const TK_TODAY = { y: 2026, m: 5, d: 24 };
function tkFmt(y, m, d) { return String(d).padStart(2, '0') + '.' + String(m + 1).padStart(2, '0') + '.' + y; }
function tkParseDate(v) {
  const mtch = /^(\d{2})\.(\d{2})\.(\d{4})$/.exec((v || '').trim());
  if (!mtch) return null;
  const d = +mtch[1], m = +mtch[2] - 1, y = +mtch[3];
  if (m < 0 || m > 11 || d < 1 || d > 31) return null;
  return { y, m, d };
}
function Calendar({ year, month, selected, onSelect }) {
  const init = selected || { y: year != null ? year : TK_TODAY.y, m: month != null ? month : TK_TODAY.m };
  const [y, setY] = React.useState(init.y);
  const [m, setM] = React.useState(init.m);
  const firstW = (new Date(y, m, 1).getDay() + 6) % 7;
  const dim = new Date(y, m + 1, 0).getDate();
  const cells = [];
  for (let i = 0; i < firstW; i++) cells.push(null);
  for (let d = 1; d <= dim; d++) cells.push(d);
  const stepMonth = (dir) => { let nm = m + dir, ny = y; if (nm < 0) { nm = 11; ny--; } if (nm > 11) { nm = 0; ny++; } setM(nm); setY(ny); };
  const nav = (dir, onClick, dbl, title) => (
    <button type="button" title={title} onClick={onClick} style={{ width: dbl ? 30 : 30, height: 30, borderRadius: 'var(--radius-sm)', border: 'none', cursor: 'pointer', background: 'transparent', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: dbl ? -4 : 0 }}
      onMouseEnter={(e) => (e.currentTarget.style.background = 'var(--bg-soft)')} onMouseLeave={(e) => (e.currentTarget.style.background = 'transparent')}>
      <Icon name={dir < 0 ? 'chevronLeft' : 'chevronRight'} size={17} color="var(--fg-3)" />
      {dbl && <Icon name={dir < 0 ? 'chevronLeft' : 'chevronRight'} size={17} color="var(--fg-3)" style={{ marginLeft: -11 }} />}
    </button>
  );
  return (
    <div style={{ width: 288, background: '#fff', borderRadius: 'var(--radius-md)', boxShadow: 'var(--shadow-popover)', padding: 14 }}>
      <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
        <div style={{ display: 'flex', gap: 2 }}>{nav(-1, () => setY(y - 1), true, 'Предыдущий год')}{nav(-1, () => stepMonth(-1), false, 'Предыдущий месяц')}</div>
        <span style={{ fontSize: 15, fontWeight: 700, color: 'var(--fg-1)' }}>{TK_MONTHS[m]} {y}</span>
        <div style={{ display: 'flex', gap: 2 }}>{nav(1, () => stepMonth(1), false, 'Следующий месяц')}{nav(1, () => setY(y + 1), true, 'Следующий год')}</div>
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7,1fr)', gap: 2, marginBottom: 4 }}>
        {TK_WD.map((w) => <div key={w} style={{ height: 28, display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 12, fontWeight: 600, color: 'var(--fg-muted)' }}>{w}</div>)}
      </div>
      <div style={{ display: 'grid', gridTemplateColumns: 'repeat(7,1fr)', gap: 2 }}>
        {cells.map((d, i) => {
          if (d == null) return <div key={i} />;
          const sel = selected && selected.y === y && selected.m === m && selected.d === d;
          const today = TK_TODAY.y === y && TK_TODAY.m === m && TK_TODAY.d === d;
          return (
            <button key={i} type="button" onClick={() => onSelect && onSelect({ y, m, d })} style={{
              height: 34, borderRadius: 'var(--radius-sm)', border: 'none', cursor: 'pointer', fontFamily: 'var(--font-sans)', fontSize: 14,
              background: sel ? 'var(--accent)' : 'transparent', color: sel ? '#fff' : 'var(--fg-1)', fontWeight: sel || today ? 700 : 400,
              boxShadow: !sel && today ? 'inset 0 0 0 1.5px var(--accent)' : 'none',
            }}
              onMouseEnter={(e) => { if (!sel) e.currentTarget.style.background = 'var(--ip-brand-green-100)'; }}
              onMouseLeave={(e) => { if (!sel) e.currentTarget.style.background = 'transparent'; }}>{d}</button>
          );
        })}
      </div>
    </div>
  );
}

// Поле даты: ввод с клавиатуры (маска дд.мм.гггг) + выпадающий календарь
function DateField({ label, value, onChange, width = 180, height = 54 }) {
  const [open, setOpen] = React.useState(false);
  const [val, setVal] = React.useState(value || '');
  const commit = (v) => { setVal(v); onChange && onChange(v); };
  const onType = (e) => {
    let s = e.target.value.replace(/[^\d.]/g, '');
    // авто-точки после дня и месяца
    const digits = s.replace(/\D/g, '').slice(0, 8);
    let out = digits.slice(0, 2);
    if (digits.length > 2) out += '.' + digits.slice(2, 4);
    if (digits.length > 4) out += '.' + digits.slice(4, 8);
    commit(out);
  };
  const parsed = tkParseDate(val);
  return (
    <div style={{ position: 'relative' }}>
      <div style={{ position: 'relative', boxSizing: 'border-box', height, width, maxWidth: '100%', borderRadius: 'var(--radius-lg)', background: '#fff', boxShadow: `inset 0 0 0 ${open ? 1.5 : 1}px ${open ? 'var(--accent)' : 'var(--border-strong)'}`, display: 'flex', alignItems: 'center', padding: '0 8px 0 18px' }}>
        {label && <span style={{ position: 'absolute', top: -8, left: 12, padding: '0 6px', background: '#fff', fontSize: 12.5, color: 'var(--fg-3)' }}>{label}</span>}
        <input value={val} onChange={onType} inputMode="numeric" placeholder="дд.мм.гггг" style={{ flex: 1, minWidth: 0, border: 'none', outline: 'none', background: 'transparent', fontFamily: 'var(--font-sans)', fontSize: 15, color: 'var(--fg-1)', paddingRight: 8 }} />
        <button type="button" title="Открыть календарь" onClick={() => setOpen((o) => !o)} style={{ width: 32, height: 32, borderRadius: 'var(--radius-sm)', border: 'none', cursor: 'pointer', background: 'transparent', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
          <Icon name="calendar" size={20} color={open ? 'var(--accent)' : 'var(--ip-muted)'} />
        </button>
      </div>
      {open && (
        <React.Fragment>
          <div onClick={() => setOpen(false)} style={{ position: 'fixed', inset: 0, zIndex: 40 }} />
          <div style={{ position: 'absolute', top: height + 6, left: 0, zIndex: 41 }}>
            <Calendar selected={parsed} onSelect={(p) => { commit(tkFmt(p.y, p.m, p.d)); setOpen(false); }} />
          </div>
        </React.Fragment>
      )}
    </div>
  );
}

// Период (диапазон дат) — два поля с календарём
function DateRange({ label = 'Период' }) {
  const [from, setFrom] = React.useState('');
  const [to, setTo] = React.useState('');
  return (
    <div style={{ display: 'inline-flex', alignItems: 'center', gap: 12, flexWrap: 'wrap' }}>
      {label && <span style={{ fontSize: 14, fontWeight: 600, color: 'var(--fg-2)' }}>{label}</span>}
      <DateField label="От" value={from} onChange={setFrom} width={170} />
      <span style={{ color: 'var(--fg-muted)' }}>—</span>
      <DateField label="До" value={to} onChange={setTo} width={170} />
    </div>
  );
}

// Строка активных фильтров
function ActiveFilters({ items = [], onRemove, onClear }) {
  if (!items.length) return null;
  return (
    <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 8 }}>
      <span style={{ fontSize: 13, color: 'var(--fg-muted)' }}>Фильтры:</span>
      {items.map((it) => (
        <span key={it.key} style={{ display: 'inline-flex', alignItems: 'center', gap: 6, height: 30, padding: '0 6px 0 12px', borderRadius: 'var(--radius-sm)', background: 'var(--ip-brand-green-100)', color: 'var(--ip-brand-green-active)', fontSize: 13, fontWeight: 600 }}>
          {it.label}
          <span onClick={() => onRemove && onRemove(it.key)} style={{ width: 18, height: 18, borderRadius: '50%', background: 'rgba(15,152,111,.16)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}><Icon name="x" size={12} color="var(--ip-brand-green-active)" /></span>
        </span>
      ))}
      {onClear && <button type="button" onClick={onClear} style={{ all: 'unset', cursor: 'pointer', fontSize: 13, fontWeight: 600, color: 'var(--accent)' }}>Очистить</button>}
    </div>
  );
}

// Сегмент-контрол (переключатель 2–4 опций) — единый для форм.
// options: массив строк или { value, label, icon }. full — растянуть на ширину.
function SegmentedControl({ value, onChange, options = [], full = false }) {
  return (
    <div style={{ display: full ? 'flex' : 'inline-flex', padding: 4, gap: 4, background: 'var(--bg-soft)', borderRadius: 'var(--radius-lg)' }}>
      {options.map((o) => {
        const opt = typeof o === 'string' ? { value: o, label: o } : o;
        const a = String(opt.value) === String(value);
        return (
          <button key={opt.value} type="button" onClick={() => onChange(opt.value)} style={{
            flex: full ? 1 : undefined, minWidth: 44, height: 42, padding: '0 16px', border: 'none', cursor: 'pointer',
            borderRadius: 'var(--radius-md)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 7,
            fontFamily: 'var(--font-sans)', fontSize: 14.5, fontWeight: 600,
            background: a ? 'var(--accent)' : 'transparent', color: a ? '#fff' : 'var(--fg-2)',
          }}>{opt.icon && <Icon name={opt.icon} size={17} color={a ? '#fff' : 'var(--fg-2)'} />}{opt.label}</button>
        );
      })}
    </div>
  );
}

// Числовой степпер (−/+) — единый для форм. unit — подпись после числа (напр. «ч.»).
function NumberStepper({ value, onChange, min = 0, max = 99, unit }) {
  const v = parseInt(value, 10) || 0;
  const set = (n) => onChange(Math.max(min, Math.min(max, n)));
  const btn = { width: 44, height: 46, border: 'none', cursor: 'pointer', borderRadius: 'var(--radius-md)', background: 'var(--ip-brand-green-100)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' };
  return (
    <div style={{ display: 'inline-flex', alignItems: 'center', gap: 10 }}>
      <button type="button" onClick={() => set(v - 1)} style={btn}><Icon name="minus" size={18} color="var(--accent)" /></button>
      <span style={{ minWidth: unit ? 54 : 34, textAlign: 'center', fontSize: 17, fontWeight: 700, color: 'var(--fg-1)' }}>{v}{unit ? ' ' + unit : ''}</span>
      <button type="button" onClick={() => set(v + 1)} style={btn}><Icon name="plus" size={18} color="var(--accent)" /></button>
    </div>
  );
}

Object.assign(window, {
  Checkbox, BulkBar, IconButton, RowMenu,
  Money, Progress, Chips, Contact, Stepper,
  Skeleton, ErrorState, Disclosure,
  FilterDropdown, Switch, RadioGroup, CheckList, DateRange, ActiveFilters,
  Calendar, DateField, Button, SegmentedControl, NumberStepper,
  // Tk*-алиасы для совместимости со старыми экранами
  TkSkeleton: Skeleton, TkErrorState: ErrorState, TkMoney: Money, TkProgress: Progress,
  TkChips: Chips, TkContact: Contact, TkButton: Button, TkCheckbox: Checkbox,
  TkDateField: DateField, TkFilterDropdown: FilterDropdown,
});
