// Ирлем-практик — «Выданные доступы» · кабинет МЕНЕДЖЕРА (десктоп)
// СТРОГО НА ДИЗАЙН-КИТЕ ТАБЛИЦ: Toolbar/TextField/DateField + Segments + ResultCount +
// Table(columns) + Pagination + Empty + Status/Person/DateTime/CellChip + IconButton.
// База наследована от таблицы ритмолога (IP-2823), сверху — менеджерские M1…M9.
const {
  Toolbar: MToolbar, TextField: MText, DateField: MDate, Segments: MSegments, ResultCount: MCount,
  Table: MTable, Status: MStatus, Person: MPerson, DateTime: MDateTime, CellChip: MChip,
  Empty: MEmpty, Pagination: MPagination, IconButton: MIconButton, Button: MBtn, Icon: MIcon,
} = window;

const MG_NOW = new Date(2026, 5, 26, 12, 0);
window.MG_NOW = MG_NOW;

const MG_GROUPS = ['Ритмология личности', 'Выращивание струн', 'Признание человечества', 'Переизлучение', 'Тестовый'];

const MG_SEED = [
  { id: 'a-1', order: '2380151', client: 'Анатолий Смирнов', login: 'a.smirnov', ritmolog: 'Елена Гордеева', group: 'Ритмология личности', count: 7, start: '24.06.2026 12:42', end: '24.06.2027 12:42' },
  { id: 'a-2', order: '2380153', client: 'Анатолий Смирнов', login: 'a.smirnov', ritmolog: 'Елена Гордеева', group: 'Выращивание струн', count: 7, start: '24.06.2026 12:42', end: '27.06.2026 12:42' },
  { id: 'a-3', order: '2380152', client: 'Марина Лебедева', login: 'm.lebedeva', ritmolog: 'Елена Гордеева', group: 'Признание человечества', count: 7, start: '24.06.2026 12:41', end: '24.06.2027 12:41' },
  { id: 'a-4', order: '2380149', client: 'Анатолий Смирнов', login: 'a.smirnov', ritmolog: 'Пётр Ковалёв', group: 'Тестовый', count: 3, start: '26.06.2026 09:10', end: '26.06.2026 21:10' },
  { id: 'a-5', order: '2379090', client: 'Михаил Королёв', login: 'mkorolev', ritmolog: 'Пётр Ковалёв', group: 'Ритмология личности', count: 7, start: '21.06.2026 20:02', end: '24.06.2027 20:02' },
  { id: 'a-6', order: '2361522', client: 'Михаил Королёв', login: 'mkorolev', ritmolog: 'Пётр Ковалёв', group: 'Тестовый', count: 1, start: '19.06.2026 09:33', end: '20.06.2026 09:33' },
  { id: 'a-7', order: '2358104', client: 'Виктория Авдеева', login: 'v.avdeeva', ritmolog: 'Ольга Тимофеева', group: 'Переизлучение', count: 5, start: '17.06.2026 14:20', end: '28.06.2026 14:20' },
  { id: 'a-8', order: '2351980', client: 'Сергей Петров', login: 's.petrov', ritmolog: 'Ольга Тимофеева', group: 'Ритмология личности', count: 7, start: '12.06.2026 11:05', end: '12.06.2027 11:05' },
  { id: 'a-9', order: '2349771', client: 'Ольга Ковалёва', login: 'o.kovaleva', ritmolog: 'Ольга Тимофеева', group: 'Выращивание струн', count: 4, start: '28.05.2026 10:15', end: '04.07.2026 10:15' },
  { id: 'a-10', order: '2347003', client: 'Дмитрий Мусихин', login: 'dmusikhin', ritmolog: 'Елена Гордеева', group: 'Признание человечества', count: 7, start: '26.05.2026 16:40', end: '27.06.2026 16:40' },
  { id: 'a-11', order: '2342118', client: 'Анна Романова', login: 'a.romanova', ritmolog: 'Ирина Соболева', group: 'Тестовый', count: 2, start: '10.05.2026 09:00', end: '15.06.2026 09:00' },
  { id: 'a-12', order: '2339980', client: 'Игорь Власов', login: 'i.vlasov', ritmolog: 'Ирина Соболева', group: 'Ритмология личности', count: 6, start: '02.05.2026 13:25', end: '02.05.2027 13:25' },
  { id: 'a-13', order: '2338501', client: 'Екатерина Ли', login: 'e.li', ritmolog: 'Ирина Соболева', group: 'Переизлучение', count: 5, start: '28.04.2026 18:10', end: '30.06.2026 18:10' },
  { id: 'a-14', order: '2335770', client: 'Павел Орлов', login: 'p.orlov', ritmolog: 'Пётр Ковалёв', group: 'Выращивание струн', count: 4, start: '20.04.2026 08:45', end: '20.04.2027 08:45' },
  { id: 'a-15', order: '2331204', client: 'Наталья Гущина', login: 'n.gushchina', ritmolog: 'Ольга Тимофеева', group: 'Ритмология личности', count: 7, start: '11.04.2026 15:30', end: '11.06.2026 15:30' },
  { id: 'a-16', order: '2329887', client: 'Сергей Петров', login: 's.petrov', ritmolog: 'Ирина Соболева', group: 'Признание человечества', count: 7, start: '02.04.2026 10:00', end: '02.04.2027 10:00' },
];

/* ── статусы и остаток: наследуются от таблицы ритмолога ── */
function mgParse(s) { const [d, t] = s.split(' '); const [dd, mm, yy] = d.split('.').map(Number); const [hh, mi] = (t || '00:00').split(':').map(Number); return new Date(yy, mm - 1, dd, hh, mi); }
const mgPlural = window.tkPlural;
function mgBucket(r) {
  const start = mgParse(r.start), end = mgParse(r.end);
  if (end <= MG_NOW) return 'expired';
  const durH = (end - start) / 3600000;
  const leftH = (end - MG_NOW) / 3600000;
  return (durH > 24 && leftH < 72) ? 'soon' : 'active';
}
function mgRemaining(r) {
  const b = mgBucket(r);
  if (b === 'expired') return { text: 'истёк', color: 'var(--ip-danger)' };
  const ms = mgParse(r.end) - MG_NOW;
  if (ms < 86400000) {
    const h = Math.floor(ms / 3600000), mi = Math.round((ms % 3600000) / 60000);
    return { text: h > 0 ? `осталось ${h} ч ${mi} мин` : `осталось ${mi} мин`, color: 'var(--ip-warning)' };
  }
  const days = Math.ceil(ms / 86400000);
  if (b === 'soon') return { text: `осталось ${days} ${mgPlural(days, 'день', 'дня', 'дней')}`, color: 'var(--ip-warning)' };
  if (days <= 45) return { text: `осталось ${days} ${mgPlural(days, 'день', 'дня', 'дней')}`, color: 'var(--fg-muted)' };
  const months = Math.round(days / 30.4);
  return { text: `осталось ~${months} ${mgPlural(months, 'месяц', 'месяца', 'месяцев')}`, color: 'var(--fg-muted)' };
}
const MG_STATUS = { active: { tone: 'success', label: 'Активен' }, soon: { tone: 'warning', label: 'Истекает' }, expired: { tone: 'error', label: 'Истёк' } };
const MG_RANK = { soon: 0, active: 1, expired: 2 };
function mgShiftToPast() { const d = new Date(MG_NOW.getTime() - 60000); const p = (n) => String(n).padStart(2, '0'); return `${p(d.getDate())}.${p(d.getMonth() + 1)}.${d.getFullYear()} ${p(d.getHours())}:${p(d.getMinutes())}`; }

/* ── шапка + навигация «вкладки-папки» (кабинет менеджера) ── */
function MgHeader() {
  return (
    <header style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px 44px', background: '#fff', boxShadow: 'var(--shadow-header)' }}>
      <img src="_pub/logo-wordmark.png" alt="Ирлем-практик" style={{ height: 34 }} />
      <div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
        <span style={{ fontSize: 14, color: 'var(--fg-muted)' }}>Менеджер</span>
        <span style={{ fontWeight: 500, fontSize: 16, color: 'var(--fg-1)' }}>Анна Т.</span>
        <img src="_pub/avatar-sample.jpg" alt="Анна Т." style={{ width: 46, height: 46, borderRadius: '50%', objectFit: 'cover' }} />
      </div>
    </header>
  );
}
function MgFolderNav() {
  const sections = ['Работа с клиентами', 'Материалы и методичка', 'Профиль'];
  const pages = ['Заказы', 'Ритмологи', 'Чат с клиентами', 'Выданные доступы'];
  return (
    <div style={{ padding: '28px 44px 0' }}>
      <div style={{ display: 'flex', gap: 6 }}>
        {sections.map((s, i) => (
          <button key={s} type="button" style={{ height: 50, padding: '0 24px', border: 'none', cursor: 'pointer', borderRadius: '12px 12px 0 0', fontFamily: 'var(--font-sans)', fontSize: 16, fontWeight: 700, whiteSpace: 'nowrap', background: i === 0 ? 'var(--ip-brand-green-100)' : 'var(--bg-soft)', color: i === 0 ? 'var(--ip-brand-green-active)' : 'var(--fg-muted)' }}>{s}</button>
        ))}
      </div>
      <div style={{ background: 'var(--ip-brand-green-100)', borderRadius: '0 12px 12px 12px', padding: '12px 14px', display: 'flex', boxShadow: 'var(--shadow-header)' }}>
        <div style={{ display: 'inline-flex', gap: 4, background: '#fff', borderRadius: 'var(--radius-lg)', padding: 5, flexWrap: 'wrap' }}>
          {pages.map((p, i) => (
            <button key={p} type="button" style={{ height: 42, padding: '0 20px', border: 'none', cursor: 'pointer', borderRadius: 'var(--radius-sm)', fontFamily: 'var(--font-sans)', fontSize: 15, fontWeight: 600, whiteSpace: 'nowrap', background: i === 3 ? 'var(--accent)' : 'transparent', color: i === 3 ? '#fff' : 'var(--ip-brand-green-active)' }}>{p}</button>
          ))}
        </div>
      </div>
    </div>
  );
}

/* ── страница ── */
function AccessManagerPage({ state = 'std' }) {
  const [rows, setRows] = React.useState(MG_SEED);
  const [seg, setSeg] = React.useState('all');
  const [query, setQuery] = React.useState('');
  const [login, setLogin] = React.useState('');
  const [ritm, setRitm] = React.useState('');
  const [order, setOrder] = React.useState('');
  const [from, setFrom] = React.useState('');
  const [to, setTo] = React.useState('');
  const [sort, setSort] = React.useState({ key: 'status', dir: 'asc' });
  const [perPage, setPerPage] = React.useState(8);
  const [page, setPage] = React.useState(1);
  const [dialog, setDialog] = React.useState(null); // {kind:'edit'|'close'|'delete', row}
  const [materials, setMaterials] = React.useState(null);
  const [toast, setToast] = React.useState(null);
  const [loading, setLoading] = React.useState(state !== 'error');
  const [failed, setFailed] = React.useState(state === 'error');
  const stick = state === 'skeleton';
  React.useEffect(() => {
    if (failed || stick) return undefined;
    const t = setTimeout(() => setLoading(false), 550);
    return () => clearTimeout(t);
  }, [failed, stick]);
  const retry = () => { setFailed(false); setLoading(true); };
  // Esc закрывает попап материалов (у модалок кита Esc уже свой)
  window.kbUseEsc(() => setMaterials(null), !!materials);

  const counts = React.useMemo(() => {
    const c = { all: rows.length, active: 0, soon: 0, expired: 0 };
    rows.forEach((r) => { c[mgBucket(r)] += 1; });
    return c;
  }, [rows]);

  const filtered = React.useMemo(() => {
    const has = (v, q) => !q.trim() || String(v).toLowerCase().includes(q.trim().toLowerCase());
    const d = (v) => { const m = /^(\d{2})\.(\d{2})\.(\d{4})$/.exec((v || '').trim()); return m ? new Date(+m[3], +m[2] - 1, +m[1]) : null; };
    const df = d(from), dt = d(to);
    let list = rows.filter((r) => {
      if (seg !== 'all' && mgBucket(r) !== seg) return false;
      if (!has(r.client, query) || !has(r.login, login) || !has(r.ritmolog, ritm) || !has(r.order, order)) return false;
      const s = mgParse(r.start);
      if (df && s < df) return false;
      if (dt && s > new Date(dt.getTime() + 86399000)) return false;
      return true;
    });
    const dir = sort.dir === 'asc' ? 1 : -1;
    return [...list].sort((a, b) => {
      if (sort.key === 'client') return a.client.localeCompare(b.client, 'ru') * dir;
      if (sort.key === 'ritmolog') return a.ritmolog.localeCompare(b.ritmolog, 'ru') * dir;
      if (sort.key === 'group') return a.group.localeCompare(b.group, 'ru') * dir;
      let av, bv;
      if (sort.key === 'order') { av = +a.order; bv = +b.order; }
      else if (sort.key === 'status') { av = MG_RANK[mgBucket(a)]; bv = MG_RANK[mgBucket(b)]; }
      else if (sort.key === 'start') { av = mgParse(a.start); bv = mgParse(b.start); }
      else { av = mgParse(a.end); bv = mgParse(b.end); }
      return (av - bv) * dir;
    });
  }, [rows, seg, query, login, ritm, order, from, to, sort]);

  React.useEffect(() => { setPage(1); }, [seg, query, login, ritm, order, from, to, perPage]);

  const total = filtered.length;
  const pages = Math.max(1, Math.ceil(total / perPage));
  const safePage = Math.min(page, pages);
  const pageRows = filtered.slice((safePage - 1) * perPage, safePage * perPage);
  const hasFilters = seg !== 'all' || [query, login, ritm, order, from, to].some((v) => v.trim() !== '');
  const clearAll = () => { setSeg('all'); setQuery(''); setLogin(''); setRitm(''); setOrder(''); setFrom(''); setTo(''); };

  /* ── действия (M4/M5/M6/M8) ── */
  const saveEdit = (patch) => {
    const r = dialog.row;
    const changedGroup = patch.group !== r.group;
    setRows((list) => list.map((x) => x.id !== r.id ? x : {
      ...x, group: patch.group,
      start: changedGroup ? `${patch.start} ${x.start.split(' ')[1]}` : `${patch.start} ${x.start.split(' ')[1]}`,
      end: `${patch.end} ${x.end.split(' ')[1]}`,
      count: changedGroup ? Math.max(1, ((MG_GROUPS.indexOf(patch.group) + 3) % 7) + 1) : x.count,
    }));
    setDialog(null);
    setToast({ tone: 'ok', title: changedGroup ? 'Доступ пересобран на новую группу' : 'Срок доступа обновлён', text: changedGroup ? `Прежний доступ «${r.group}» закрыт, клиент получил материалы группы «${patch.group}».` : 'Статус, остаток и счётчики вкладок пересчитаны.' });
  };
  const doClose = () => {
    const r = dialog.row;
    if (mgBucket(r) === 'expired') { setDialog(null); setToast({ tone: 'ok', title: 'Доступ уже истёк', text: 'Изменений не потребовалось.' }); return; }
    setRows((list) => list.map((x) => x.id === r.id ? { ...x, end: mgShiftToPast() } : x));
    setDialog(null);
    setToast({ tone: 'ok', title: 'Доступ закрыт', text: 'Клиент потерял материалы, строка перешла во вкладку «Истёкшие».' });
  };
  const doDelete = () => {
    const r = dialog.row;
    setRows((list) => list.filter((x) => x.id !== r.id));
    setDialog(null);
    setToast({ tone: 'ok', title: 'Доступ удалён', text: 'Строка удалена из всех вкладок без возможности восстановления.' });
  };
  const exportCsv = () => {
    setToast({ tone: 'progress', title: 'Готовим файл…', text: `Выгружаем ${total} ${mgPlural(total, 'доступ', 'доступа', 'доступов')} текущей выборки, со столбцом «Статус».` });
    setTimeout(() => setToast({ tone: 'ok', title: 'Файл готов', text: 'dostupy_2026-06-26.csv', action: { label: 'Скачать', onClick: () => setToast(null) } }), 1200);
  };

  const segments = [
    { key: 'all', label: 'Все', count: counts.all },
    { key: 'active', label: 'Активные', count: counts.active },
    { key: 'soon', label: 'Истекают скоро', count: counts.soon, tone: 'warning' },
    { key: 'expired', label: 'Истёкшие', count: counts.expired },
  ];

  const columns = [
    { key: 'order', label: 'ID заказа', w: '92px', min: 92, sortable: true, render: (r) => <a href="#" onClick={(e) => e.preventDefault()} style={{ fontSize: 15, fontWeight: 700, color: 'var(--accent)' }}>{r.order}</a> },
    { key: 'status', label: 'Статус', w: '112px', min: 112, sortable: true, render: (r) => { const m = MG_STATUS[mgBucket(r)]; return <MStatus tone={m.tone}>{m.label}</MStatus>; } },
    { key: 'client', label: 'Клиент', w: 'minmax(156px,1.2fr)', min: 156, sortable: true, render: (r) => <MPerson name={r.client} sub={r.login} /> },
    { key: 'ritmolog', label: 'Ритмолог', w: 'minmax(120px,1fr)', min: 120, sortable: true, render: (r) => <a href="#" onClick={(e) => e.preventDefault()} style={{ fontSize: 14.5, color: 'var(--accent)' }}>{r.ritmolog}</a> },
    { key: 'group', label: 'Группа', w: 'minmax(126px,1fr)', min: 126, sortable: true, render: (r) => <div style={{ fontSize: 14.5, color: 'var(--fg-1)', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }} title={r.group}>{r.group}</div> },
    { key: 'count', label: 'Материалы', w: '104px', min: 104, render: (r) => <MChip icon="subject" onClick={() => setMaterials(mgBucket(r) === 'expired' ? 'expired' : 'active')}>{r.count} мат.</MChip> },
    { key: 'start', label: 'Начало', w: '126px', min: 126, sortable: true, render: (r) => <MDateTime value={r.start} /> },
    { key: 'end', label: 'Окончание', w: '132px', min: 132, sortable: true, render: (r) => { const rem = mgRemaining(r); return <MDateTime value={r.end} note={rem.text} noteColor={rem.color} />; } },
    { key: 'actions', label: 'Действия', w: '118px', min: 118, render: (r) => (
      <div style={{ display: 'flex', gap: 2 }}>
        <MIconButton icon="pencil" title="Редактировать" onClick={() => setDialog({ kind: 'edit', row: r })} />
        <MIconButton icon="ban" title="Закрыть доступ" disabled={mgBucket(r) === 'expired'} onClick={() => setDialog({ kind: 'close', row: r })} />
        <MIconButton icon="trash" title="Удалить" tone="danger" onClick={() => setDialog({ kind: 'delete', row: r })} />
      </div>
    ) },
  ];

  return (
    <div style={{ background: 'var(--bg-app)', minHeight: '100%' }}>
      <MgHeader />
      <div style={{ padding: '32px 44px 56px', display: 'flex', flexDirection: 'column', gap: 16 }}>
        <div style={{ display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between', gap: 20, flexWrap: 'wrap' }}>
          <div>
            <h1 style={{ margin: 0, fontSize: 28, fontWeight: 700, color: 'var(--fg-1)' }}>Выданные доступы</h1>
            <div style={{ marginTop: 6, fontSize: 15, color: 'var(--fg-muted)' }}>Доступы к материалам по всем ритмологам</div>
          </div>
          <MBtn color="white" icon="download" onClick={exportCsv}>Экспорт CSV</MBtn>
        </div>

        <MToolbar query={query} setQuery={setQuery} placeholder="Поиск по ФИО клиента"
          suggestions={[...new Set(MG_SEED.map((r) => r.client))]}>
          <MText label="Ритмолог" value={ritm} onChange={setRitm} placeholder="Введите 2 символа" width="auto" flex="1 1 0" minWidth={280}
            icon="user" suggestions={[...new Set(MG_SEED.map((r) => r.ritmolog))]} />
          <div style={{ width: '100%', display: 'flex', alignItems: 'center', gap: 16, flexWrap: 'wrap' }}>
            <MText label="Логин клиента" value={login} onChange={setLogin} width={200} />
            <MText label="ID заказа" value={order} onChange={setOrder} width={170} icon="hash" />
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <MDate label="Доступ с" value={from} onChange={setFrom} width={165} />
              <span style={{ color: 'var(--fg-muted)' }}>—</span>
              <MDate label="по" value={to} onChange={setTo} width={165} />
            </div>
            {hasFilters && (
              <button type="button" onClick={clearAll} style={{ marginLeft: 'auto', height: 48, padding: '0 18px', border: 'none', cursor: 'pointer', borderRadius: 'var(--radius-lg)', background: 'var(--bg-soft)', fontFamily: 'var(--font-sans)', fontSize: 15, fontWeight: 600, color: 'var(--fg-2)', display: 'inline-flex', alignItems: 'center', gap: 6 }}>
                <MIcon name="x" size={18} color="var(--fg-2)" />Сбросить
              </button>
            )}
          </div>
        </MToolbar>

        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, flexWrap: 'wrap' }}>
          <MSegments value={seg} onChange={setSeg} segments={segments} />
          <MCount n={total} noun={['доступ', 'доступа', 'доступов']} />
        </div>

        {failed
          ? <window.ErrorState onRetry={retry} />
          : loading
          ? <window.Skeleton rows={8} cols={9} />
          : <MTable columns={columns} rows={pageRows} sort={sort} setSort={setSort} keyField="id"
              empty={<MEmpty onClear={clearAll} title="Доступы не найдены" hint="Измените фильтры или сбросьте их, чтобы увидеть все доступы." />}
              footer={total > 0 ? <MPagination page={safePage} setPage={setPage} perPage={perPage} setPerPage={setPerPage} total={total} /> : null} />}
      </div>

      {dialog && (dialog.kind === 'edit'
        ? <window.MgEditDialog row={dialog.row} groups={MG_GROUPS} onCancel={() => setDialog(null)} onSave={saveEdit} />
        : <window.MgConfirmDialog kind={dialog.kind} row={dialog.row} onCancel={() => setDialog(null)} onConfirm={dialog.kind === 'close' ? doClose : doDelete} />)}

      {materials && (
        <div style={{ position: 'fixed', inset: 0, zIndex: 110, background: 'rgba(15,40,33,.42)', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 40 }} onClick={() => setMaterials(null)}>
          <div onClick={(e) => e.stopPropagation()}>
            <window.MaterialsAccessPopup which={materials} onClose={() => setMaterials(null)} />
          </div>
        </div>
      )}

      <window.MgToast toast={toast} onClose={() => setToast(null)} />
    </div>
  );
}

window.AccessManagerPage = AccessManagerPage;
