// test-flow.jsx — редактор, прохождение, проверка, статистика

const emptyQuestion = (type = 'choice') => ({
  id: null,
  type,
  text: '',
  weight: 0,
  weightManual: false,
  explanation: '',
  options: type === 'choice'
    ? [{ text: '', isCorrect: true }, { text: '', isCorrect: false }, { text: '', isCorrect: false }, { text: '', isCorrect: false }]
    : [],
});

const redistributeLocal = (questions) => {
  if (!questions.length) return questions;
  const manualSum = questions
    .filter((q) => q.weightManual)
    .reduce((s, q) => s + (q.weight || 0), 0);
  const auto = questions.filter((q) => !q.weightManual);
  if (!auto.length) return questions;
  const remaining = Math.max(0, 100 - manualSum);
  const base = Math.floor(remaining / auto.length);
  let rest = remaining - base * auto.length;
  return questions.map((q) => {
    if (q.weightManual) return q;
    const w = base + (rest > 0 ? 1 : 0);
    if (rest > 0) rest -= 1;
    return { ...q, weight: w };
  });
};

const weightSum = (questions) => (questions || []).reduce((s, q) => s + (q.weight || 0), 0);

const qKey = (q, idx) => q.id || `q-${idx}`;

const isQuestionAnswered = (q, idx, answerMap) => {
  const a = answerMap[qKey(q, idx)];
  if (q.type === 'open') return Boolean(a?.openText?.trim());
  return Boolean(a?.optionId ?? a?.optionIndex != null);
};

const answersFromSaved = (questions, savedAnswers = []) => {
  const map = {};
  savedAnswers.forEach((a) => {
    const idx = questions.findIndex((q) => q.id === a.questionId);
    if (idx < 0) return;
    map[qKey(questions[idx], idx)] = {
      optionId: a.optionId || undefined,
      openText: a.openText || '',
    };
  });
  return map;
};

const formatDurationSec = (sec) => {
  if (sec == null || sec < 0) return '—';
  const m = Math.floor(sec / 60);
  const s = sec % 60;
  if (m <= 0) return `${s} сек`;
  return s ? `${m} мин ${s} сек` : `${m} мин`;
};

const isAnswerSelected = (answer, q, option, optionIndex) => {
  if (!answer) return false;
  if (q.id && option.id) return answer.optionId === option.id;
  return answer.optionIndex === optionIndex;
};

const truncate = (text, max = 48) => {
  const t = (text || '').trim();
  if (!t) return 'Без текста';
  return t.length > max ? `${t.slice(0, max)}…` : t;
};

const moveInList = (list, from, to) => {
  if (from === to || from < 0 || to < 0 || from >= list.length || to >= list.length) return list;
  const next = [...list];
  const [item] = next.splice(from, 1);
  next.splice(to, 0, item);
  return next;
};

const TestPageShell = ({ backLabel, onBack, title, titleItalic, subtitle, actions, banner, children }) => (
  <div className="page test-page">
    <div className="test-page-top">
      <div className="test-page-intro">
        {onBack && (
          <button type="button" className="btn btn-ghost btn-sm test-page-back" onClick={onBack}>
            <Icon name="arrowLeft" size={14} /> {backLabel || 'Назад'}
          </button>
        )}
        <h1 className="page-title">
          {title}{titleItalic && <> <span className="page-title-italic">{titleItalic}</span></>}
        </h1>
        {subtitle && <p className="page-sub">{subtitle}</p>}
      </div>
      {actions && <div className="test-page-actions">{actions}</div>}
    </div>
    {banner}
    {children}
  </div>
);

const MAX_OPTIONS = 4;
const MIN_OPTIONS = 2;

const QuestionEditorCard = ({
  q, idx, total, dragIdx, dropIdx, onDragStart, onDragOver, onDrop, onDragEnd,
  onMoveUp, onMoveDown, onRemove, onChange, onAddOption, onRemoveOption,
}) => (
  <div
    className={`test-q-card card card-pad${dragIdx === idx ? ' is-dragging' : ''}${dropIdx === idx ? ' is-drop-target' : ''}`}
    onDragOver={(e) => { e.preventDefault(); onDragOver?.(idx); }}
    onDrop={(e) => { e.preventDefault(); onDrop?.(idx); }}
  >
    <div className="test-q-card-top">
      <div className="test-q-card-left">
        <button
          type="button"
          className="test-q-drag"
          draggable
          title="Перетащите для смены порядка"
          onDragStart={(e) => { e.dataTransfer.effectAllowed = 'move'; onDragStart?.(idx); }}
          onDragEnd={onDragEnd}
        >
          <Icon name="drag" size={14} />
        </button>
        <span className="test-q-index">Вопрос {idx + 1}</span>
        <span className={`test-q-type-tag test-q-type-tag--${q.type}`}>
          {q.type === 'open' ? 'Открытый' : 'С вариантами'}
        </span>
      </div>
      <div className="test-q-card-tools">
        <div className="test-q-reorder">
          <button type="button" className="test-q-tool-btn" disabled={idx === 0} title="Выше" onClick={() => onMoveUp?.(idx)}>
            <Icon name="arrowUp" size={14} />
          </button>
          <button type="button" className="test-q-tool-btn" disabled={idx === total - 1} title="Ниже" onClick={() => onMoveDown?.(idx)}>
            <Icon name="arrowDown" size={14} />
          </button>
        </div>
        <label className="test-q-weight">
          <span>Вес</span>
          <input type="number" min="0" max="100" value={q.weight ?? 0}
            onChange={(e) => onChange(idx, {
              weight: Math.min(100, Math.max(0, parseInt(e.target.value, 10) || 0)),
              weightManual: true,
            })} />
          <span className="test-q-weight-suffix">%</span>
        </label>
        <button type="button" className="test-q-tool-btn test-q-tool-btn--danger" title="Удалить" onClick={() => onRemove(idx)}>
          <Icon name="trash" size={14} />
        </button>
      </div>
    </div>

    <label className="settings-field test-q-field">
      <span>Текст вопроса</span>
      <textarea className="test-q-text" rows={2} placeholder={`Введите текст вопроса ${idx + 1}…`}
        value={q.text} onChange={(e) => onChange(idx, { text: e.target.value })} />
    </label>

    {q.type === 'choice' && (
      <div className="test-q-options">
        <span className="test-q-options-label">Варианты ответа · отметьте правильный</span>
        {(q.options || []).map((o, oi) => (
          <div key={oi} className={`test-option-item${o.isCorrect ? ' is-correct' : ''}`}>
            <button
              type="button"
              className={`test-option-radio${o.isCorrect ? ' is-checked' : ''}`}
              title="Правильный ответ"
              onClick={() => onChange(idx, {
                options: q.options.map((opt, j) => ({ ...opt, isCorrect: j === oi })),
              })}
            >
              {o.isCorrect && <Icon name="check" size={12} />}
            </button>
            <input
              className="test-option-input"
              value={o.text}
              placeholder={`Вариант ${oi + 1}`}
              onChange={(e) => onChange(idx, {
                options: q.options.map((opt, j) => (j === oi ? { ...opt, text: e.target.value } : opt)),
              })}
            />
            {(q.options || []).length > MIN_OPTIONS && (
              <button
                type="button"
                className="test-option-remove"
                title="Удалить вариант"
                onClick={() => onRemoveOption?.(idx, oi)}
              >
                <Icon name="minus" size={14} />
              </button>
            )}
          </div>
        ))}
        {(q.options || []).length < MAX_OPTIONS && (
          <button type="button" className="test-option-add" onClick={() => onAddOption?.(idx)}>
            <Icon name="plus" size={12} /> Добавить вариант
          </button>
        )}
      </div>
    )}
  </div>
);

const TestEditor = ({ testId, articleId, onBack, onPublished }) => {
  const [test, setTest] = React.useState(null);
  const [preview, setPreview] = React.useState(false);
  const [saving, setSaving] = React.useState(false);
  const [savedHint, setSavedHint] = React.useState('');
  const [dragIdx, setDragIdx] = React.useState(null);
  const [dropIdx, setDropIdx] = React.useState(null);
  const [addMenuOpen, setAddMenuOpen] = React.useState(false);
  const addMenuRef = React.useRef(null);

  React.useEffect(() => {
    if (!addMenuOpen) return;
    const close = (e) => {
      if (addMenuRef.current && !addMenuRef.current.contains(e.target)) setAddMenuOpen(false);
    };
    document.addEventListener('mousedown', close);
    return () => document.removeEventListener('mousedown', close);
  }, [addMenuOpen]);

  React.useEffect(() => {
    (async () => {
      if (testId) {
        const t = await window.KB.getTest(testId);
        const list = t.questionList?.length
          ? t.questionList.map((q) => ({ ...q, weightManual: q.weightManual ?? Boolean(q.weight) }))
          : [emptyQuestion()];
        setTest({ ...t, questionList: list });
      } else if (articleId) {
        const a = window.findArticle(articleId);
        setTest({
          title: a ? `Тест: ${a.title}` : 'Новый тест',
          articleId,
          articleTitle: a?.title,
          passing: 80,
          duration: 15,
          shuffleQuestions: false,
          showAnswers: true,
          status: 'draft',
          questionList: [emptyQuestion()],
        });
      }
    })();
  }, [testId, articleId]);

  if (!test) {
    return (
      <TestPageShell backLabel="К тестам" onBack={onBack} title="Редактор" titleItalic="теста">
        <div className="settings-info-box">Загрузка…</div>
      </TestPageShell>
    );
  }

  const questions = test.questionList || [];
  const sum = weightSum(questions);

  const validateWeights = async () => {
    if (!questions.length) return true;
    if (sum === 100) return true;
    await window.UI.alert({
      title: 'Проверьте веса',
      message: `Сумма весов всех вопросов: ${sum}%. Должно быть ровно 100%. Используйте «Пересчитать» или задайте вручную.`,
      icon: 'info',
    });
    return false;
  };

  const setQuestions = (updater) => {
    setTest((t) => {
      const list = typeof updater === 'function' ? updater(t.questionList || []) : updater;
      return { ...t, questionList: list };
    });
  };

  const setQ = (idx, patch) => {
    setQuestions((list) => {
      const next = [...list];
      next[idx] = { ...next[idx], ...patch };
      return next;
    });
  };

  const addOption = (qIdx) => {
    setQ(qIdx, {
      options: [...(questions[qIdx].options || []), { text: '', isCorrect: false }].slice(0, MAX_OPTIONS),
    });
  };

  const removeOption = (qIdx, oIdx) => {
    const opts = (questions[qIdx].options || []).filter((_, j) => j !== oIdx);
    if (opts.length < MIN_OPTIONS) return;
    if (!opts.some((o) => o.isCorrect)) opts[0] = { ...opts[0], isCorrect: true };
    setQ(qIdx, { options: opts });
  };

  const addQuestion = (type) => {
    setQuestions((list) => [...list, emptyQuestion(type)]);
  };

  const removeQuestion = async (idx) => {
    const ok = await window.UI.confirm({
      title: 'Удалить вопрос?',
      message: `Вопрос ${idx + 1} будет удалён без возможности восстановления.`,
      confirmLabel: 'Удалить',
      cancelLabel: 'Отмена',
      danger: true,
      icon: 'trash',
    });
    if (!ok) return;
    setQuestions((list) => list.filter((_, i) => i !== idx));
  };

  const moveQuestion = (from, to) => {
    setQuestions((list) => moveInList(list, from, to));
  };

  const recalcWeights = () => {
    setQuestions((list) => redistributeLocal(list));
  };

  const handleDelete = async () => {
    if (!test?.id) {
      onBack?.();
      return;
    }
    const ok = await window.UI.confirm({
      title: 'Удалить тест?',
      message: `«${test.title}» будет удалён вместе с попытками и результатами.`,
      confirmLabel: 'Удалить',
      cancelLabel: 'Отмена',
      danger: true,
      icon: 'trash',
    });
    if (!ok) return;
    try {
      await window.KB.deleteTest(test.id);
      onBack?.();
    } catch (e) {
      await window.UI.alert({ title: 'Ошибка', message: e.message, icon: 'info' });
    }
  };

  const handleBack = async () => {
    if (!test?.id) {
      onBack?.();
      return;
    }
    const hasContent = questions.some((q) => q.text?.trim() || (q.options || []).some((o) => o.text?.trim()));
    if (test.status === 'draft' && !hasContent && !savedHint) {
      const drop = await window.UI.confirm({
        title: 'Закрыть без сохранения?',
        message: 'Черновик теста будет удалён.',
        confirmLabel: 'Закрыть',
        cancelLabel: 'Остаться',
        icon: 'info',
      });
      if (!drop) return;
      try {
        await window.KB.deleteTest(test.id);
      } catch { /* ignore */ }
    }
    onBack?.();
  };

  const save = async (publish = false) => {
    if (!(await validateWeights())) return;
    if (publish) {
      const ok = await window.UI.confirm({
        title: 'Опубликовать тест?',
        message: 'После публикации сотрудники смогут пройти тест после ознакомления со статьёй.',
        confirmLabel: 'Опубликовать',
        cancelLabel: 'Отмена',
        icon: 'quiz',
      });
      if (!ok) return;
    }
    setSaving(true);
    setSavedHint('');
    try {
      const payload = { ...test, questionList: questions.map(({ weightManual, ...q }) => q) };
      const saved = test.id
        ? await window.KB.saveTest(payload)
        : await window.KB.saveTest({ ...payload, articleId: test.articleId });
      setTest({
        ...saved,
        questionList: (saved.questionList || questions).map((q, i) => {
          const prev = q.id ? questions.find((p) => p.id === q.id) : questions[i];
          return { ...q, weightManual: prev?.weightManual ?? Boolean(q.weight) };
        }),
      });
      if (publish) {
        await window.KB.publishTest(saved.id);
        await window.UI.alert({
          title: 'Тест опубликован',
          message: 'Сотрудники пройдут тест через блок «Обязательно к прочтению» в статье.',
          icon: 'check',
        });
        onPublished?.(saved);
      } else {
        setSavedHint('Сохранено');
        setTimeout(() => setSavedHint(''), 2500);
      }
    } catch (e) {
      await window.UI.alert({ title: 'Ошибка', message: e.message, icon: 'info' });
    } finally {
      setSaving(false);
    }
  };

  if (preview) {
    return (
      <TestTakeFlow
        test={{ ...test, questionList: questions, status: 'published' }}
        preview
        onBack={() => setPreview(false)}
      />
    );
  }

  return (
    <TestPageShell
      backLabel="К тестам"
      onBack={handleBack}
      title="Редактор"
      titleItalic="теста"
      subtitle={test.articleTitle ? `Статья: ${test.articleTitle}` : null}
      actions={(
        <>
          {savedHint && <span className="test-save-hint">{savedHint}</span>}
          {test.id && (
            <button type="button" className="btn btn-ghost test-btn-delete" onClick={handleDelete}>Удалить</button>
          )}
          <button type="button" className="btn btn-ghost" onClick={() => setPreview(true)} disabled={!questions.length}>Превью</button>
          <button type="button" className="btn btn-ghost" onClick={() => save(false)} disabled={saving}>Сохранить</button>
          <button type="button" className="btn btn-primary" onClick={() => save(true)} disabled={saving}>
            {saving ? '…' : 'Опубликовать'}
          </button>
        </>
      )}
    >
      <div className="test-editor-layout">
        <aside className="card card-pad test-settings-panel">
          <h3 className="test-panel-title">Настройки</h3>
          <div className="settings-form">
            <label className="settings-field">
              <span>Название</span>
              <input value={test.title || ''} onChange={(e) => setTest((t) => ({ ...t, title: e.target.value }))} />
            </label>
            {test.articleTitle && (
              <label className="settings-field">
                <span>Связанная статья</span>
                <div className="settings-readonly-field">{test.articleTitle}</div>
              </label>
            )}
            <div className="settings-form-row">
              <label className="settings-field">
                <span>Проходной балл, %</span>
                <input type="number" min="1" max="100" value={test.passing ?? 80}
                  onChange={(e) => setTest((t) => ({ ...t, passing: parseInt(e.target.value, 10) || 80 }))} />
              </label>
              <label className="settings-field">
                <span>Время, мин</span>
                <input type="number" min="1" value={test.duration ?? 15}
                  onChange={(e) => setTest((t) => ({ ...t, duration: parseInt(e.target.value, 10) || 15 }))} />
              </label>
            </div>
            <label className="settings-perm-row">
              <input type="checkbox" checked={Boolean(test.shuffleQuestions)}
                onChange={(e) => setTest((t) => ({ ...t, shuffleQuestions: e.target.checked }))} />
              <span><strong>Перемешивать вопросы</strong><span className="text-xs text-3">При прохождении порядок меняется</span></span>
            </label>
            <label className="settings-perm-row">
              <input type="checkbox" checked={test.showAnswers !== false}
                onChange={(e) => setTest((t) => ({ ...t, showAnswers: e.target.checked }))} />
              <span><strong>Показывать правильные ответы</strong><span className="text-xs text-3">После завершения теста</span></span>
            </label>
          </div>
        </aside>

        <section className="test-questions-panel">
          <div className="test-questions-head">
            <div>
              <h3 className="test-panel-title">Вопросы</h3>
              <p className="test-panel-sub">{questions.length} {questions.length === 1 ? 'вопрос' : questions.length < 5 ? 'вопроса' : 'вопросов'}</p>
            </div>
          </div>

          {questions.length > 0 && (
            <div className={`test-weight-bar${sum === 100 ? ' is-ok' : ''}`}>
              <span>Сумма весов: <strong>{sum}%</strong> {sum === 100 ? '· OK' : `· нужно 100%`}</span>
              {sum !== 100 && (
                <button type="button" className="btn btn-ghost btn-sm" onClick={recalcWeights}>Пересчитать</button>
              )}
            </div>
          )}

          <div className="test-questions-list">
            {questions.map((q, idx) => (
              <QuestionEditorCard
                key={q.id || `new-${idx}`}
                q={q}
                idx={idx}
                total={questions.length}
                dragIdx={dragIdx}
                dropIdx={dropIdx}
                onDragStart={setDragIdx}
                onDragOver={setDropIdx}
                onDrop={(to) => {
                  if (dragIdx !== null) moveQuestion(dragIdx, to);
                  setDragIdx(null);
                  setDropIdx(null);
                }}
                onDragEnd={() => { setDragIdx(null); setDropIdx(null); }}
                onMoveUp={(i) => moveQuestion(i, i - 1)}
                onMoveDown={(i) => moveQuestion(i, i + 1)}
                onRemove={removeQuestion}
                onChange={setQ}
                onAddOption={addOption}
                onRemoveOption={removeOption}
              />
            ))}
          </div>

          <div className="test-add-row" ref={addMenuRef}>
            <button
              type="button"
              className="test-add-btn"
              title="Добавить вопрос"
              onClick={() => setAddMenuOpen((v) => !v)}
            >
              <Icon name="plus" size={16} />
            </button>
            {addMenuOpen && (
              <div className="test-add-menu">
                <button type="button" className="test-add-menu-item" onClick={() => { addQuestion('choice'); setAddMenuOpen(false); }}>
                  <Icon name="help" size={14} /> С вариантами
                </button>
                <button type="button" className="test-add-menu-item" onClick={() => { addQuestion('open'); setAddMenuOpen(false); }}>
                  <Icon name="text" size={14} /> Открытый
                </button>
              </div>
            )}
          </div>

          {!questions.length && (
            <div className="settings-info-box">Добавьте первый вопрос — с вариантами или открытый.</div>
          )}
        </section>
      </div>
    </TestPageShell>
  );
};

const formatTestTime = (sec) => {
  const m = Math.floor(sec / 60);
  const s = sec % 60;
  return `${m}:${String(s).padStart(2, '0')}`;
};

const TestTakeFlow = ({ testId, test: testProp, preview, autoStart, resume, onBack, onDone, onLockChange }) => {
  const [screen, setScreen] = React.useState(autoStart ? 'starting' : 'welcome');
  const [test, setTest] = React.useState(testProp || null);
  const [attemptId, setAttemptId] = React.useState(null);
  const [startedAt, setStartedAt] = React.useState(null);
  const [answers, setAnswers] = React.useState({});
  const [current, setCurrent] = React.useState(0);
  const [result, setResult] = React.useState(null);
  const [attempts, setAttempts] = React.useState([]);
  const [starting, setStarting] = React.useState(false);
  const [timeLeft, setTimeLeft] = React.useState(null);
  const finishingRef = React.useRef(false);
  const answersRef = React.useRef(answers);
  answersRef.current = answers;

  const applyLock = React.useCallback((lock) => {
    window.KB.setTestLock(lock);
    onLockChange?.(lock);
  }, [onLockChange]);

  const clearLock = React.useCallback(() => {
    window.KB.clearTestLock();
    onLockChange?.(null);
  }, [onLockChange]);

  const handleBack = async (dest) => {
    if (screen === 'questions' && !preview && attemptId) {
      await window.UI.alert({
        title: 'Тест не завершён',
        message: 'Во время прохождения выйти нельзя. Завершите тест или дождитесь окончания времени.',
        icon: 'quiz',
      });
      return;
    }
    clearLock();
    onBack?.(dest);
  };

  const resumeAttempt = React.useCallback((data) => {
    setAttemptId(data.attemptId);
    setStartedAt(data.startedAt);
    setTest(data.test);
    setAnswers(answersFromSaved(data.test?.questionList || [], data.savedAnswers || []));
    setCurrent(0);
    setScreen('questions');
    applyLock({
      inProgress: true,
      testId: data.test?.id || data.testId,
      articleId: data.test?.articleId || data.articleId,
      attemptId: data.attemptId,
    });
  }, [applyLock]);

  React.useEffect(() => {
    if (testProp) {
      setTest(testProp);
      setAnswers({});
      setCurrent(0);
      setScreen(preview ? 'questions' : (autoStart ? 'starting' : 'welcome'));
      return;
    }
    (async () => {
      const t = await window.KB.getTest(testId);
      setTest(t);
      const list = await window.KB.getMyAttempts(testId);
      setAttempts(list);
      const active = await window.KB.getActiveTestAttempt();
      if (active?.testId === testId) resumeAttempt(active);
    })();
  }, [testId, testProp, resumeAttempt, resume]);

  const start = async () => {
    if (preview) {
      setScreen('questions');
      return;
    }
    if (starting) return;
    setStarting(true);
    finishingRef.current = false;
    try {
      const data = await window.KB.startTestAttempt(test.id);
      setAttemptId(data.attemptId);
      setStartedAt(data.startedAt || new Date().toISOString());
      setTest(data.test);
      setAnswers(answersFromSaved(data.test?.questionList || [], data.savedAnswers || []));
      setCurrent(0);
      setScreen('questions');
      applyLock({
        inProgress: true,
        testId: test.id,
        articleId: test.articleId,
        attemptId: data.attemptId,
      });
    } catch (e) {
      await window.UI.alert({ title: 'Не удалось начать тест', message: e.message, icon: 'quiz' });
      const list = await window.KB.getMyAttempts(test.id).catch(() => []);
      setAttempts(list);
    } finally {
      setStarting(false);
    }
  };

  React.useEffect(() => {
    if (screen !== 'questions' || preview || !attemptId) return undefined;
    const handler = (e) => {
      e.preventDefault();
      e.returnValue = '';
    };
    window.addEventListener('beforeunload', handler);
    return () => window.removeEventListener('beforeunload', handler);
  }, [screen, preview, attemptId]);

  React.useEffect(() => {
    if (!autoStart || preview || !test || screen !== 'starting') return;
    start().catch(async (e) => {
      await window.UI.alert({ title: 'Ошибка', message: e.message });
      setScreen('welcome');
    });
  }, [autoStart, test, preview, screen]);

  React.useEffect(() => {
    if (screen !== 'questions' || preview || !attemptId || !test?.id) return undefined;
    const payload = (test.questionList || []).map((q, i) => {
      const a = answers[qKey(q, i)];
      return {
        questionId: q.id,
        optionId: a?.optionId,
        openText: a?.openText || '',
      };
    }).filter((p) => p.questionId);
    const timer = setTimeout(() => {
      window.KB.submitTestAttempt(test.id, attemptId, payload, false).catch(() => {});
    }, 500);
    return () => clearTimeout(timer);
  }, [answers, screen, preview, attemptId, test?.id, test?.questionList]);

  const questions = test?.questionList || [];

  const finishRef = React.useRef(async () => {});
  finishRef.current = async (forced = false) => {
    if (!test || finishingRef.current) return;

    const answerMap = answersRef.current;
    const allAnswered = questions.every((q, i) => isQuestionAnswered(q, i, answerMap));

    if (!preview && !forced && !allAnswered) return;

    if (!preview && !forced) {
      const ok = await window.UI.confirm({
        title: 'Завершить тест?',
        message: 'После отправки изменить ответы будет нельзя.',
        confirmLabel: 'Завершить',
        cancelLabel: 'Отмена',
        icon: 'quiz',
      });
      if (!ok) return;
    }

    if (preview) {
      let correct = 0;
      let gradable = 0;
      questions.forEach((q, i) => {
        if (q.type !== 'choice') return;
        gradable += 1;
        const a = answerMap[qKey(q, i)];
        const opt = q.options?.[a?.optionIndex];
        if (opt?.isCorrect) correct += 1;
      });
      setResult({
        score: gradable ? Math.round((correct / gradable) * 100) : 0,
        passed: gradable ? Math.round((correct / gradable) * 100) >= (test.passing || 80) : false,
        correctCount: correct,
        totalGradable: gradable,
        preview: true,
      });
      setScreen('results');
      return;
    }

    finishingRef.current = true;
    setTimeLeft(null);

    const payload = questions.map((q, i) => {
      const a = answerMap[qKey(q, i)];
      return {
        questionId: q.id,
        optionId: a?.optionId,
        openText: a?.openText || '',
      };
    }).filter((p) => p.questionId);

    try {
      const res = await window.KB.submitTestAttempt(test.id, attemptId, payload, true);
      const correctCount = (res.answers || []).filter((a) => a.isCorrect === true).length;
      const totalGradable = (res.answers || []).filter((a) => a.isCorrect != null).length;
      setResult({ ...res, correctCount, totalGradable, timedOut: forced });
      const list = await window.KB.getMyAttempts(test.id).catch(() => []);
      setAttempts(list);
      setScreen('results');
      clearLock();
      onDone?.();
    } catch (e) {
      finishingRef.current = false;
      await window.UI.alert({ title: 'Ошибка', message: e.message });
    }
  };

  const finish = (forced) => finishRef.current(forced === true);

  React.useEffect(() => {
    if (screen !== 'questions' || preview || !test?.duration || !attemptId || !startedAt) {
      setTimeLeft(null);
      return undefined;
    }
    const deadline = new Date(startedAt).getTime() + test.duration * 60 * 1000;
    const tick = () => {
      const left = Math.max(0, Math.ceil((deadline - Date.now()) / 1000));
      setTimeLeft(left);
      if (left <= 0) finishRef.current(true);
    };
    tick();
    const id = setInterval(tick, 1000);
    return () => clearInterval(id);
  }, [screen, test?.id, test?.duration, preview, attemptId, startedAt]);

  if (!test || screen === 'starting') {
    return (
      <TestPageShell backLabel="К тестам" onBack={handleBack} title="Загрузка" titleItalic="теста">
        <div className="settings-info-box">Подготовка теста…</div>
      </TestPageShell>
    );
  }

  const allAnswered = questions.every((q, i) => isQuestionAnswered(q, i, answers));
  const effectiveAttempt = window.KB.getEffectiveTestAttempt(attempts);
  const canRetake = window.KB.canRetakeTest(attempts);

  if (screen === 'welcome') {
    return (
      <TestPageShell backLabel="К тестам" onBack={handleBack} title={test.title}>
        <div className="test-welcome card card-pad">
          <div className="test-welcome-stats">
            <div className="test-welcome-stat">
              <span className="test-welcome-stat-value">{questions.length}</span>
              <span className="test-welcome-stat-label">вопросов</span>
            </div>
            <div className="test-welcome-stat">
              <span className="test-welcome-stat-value">{test.passing}%</span>
              <span className="test-welcome-stat-label">проходной</span>
            </div>
            {test.duration > 0 && (
              <div className="test-welcome-stat">
                <span className="test-welcome-stat-value">{test.duration}</span>
                <span className="test-welcome-stat-label">минут</span>
              </div>
            )}
          </div>
          {effectiveAttempt && (
            <div className="settings-info-box">
              {effectiveAttempt.status === 'pending_review'
                ? 'Ответы на проверке — дождитесь результата автора теста'
                : effectiveAttempt.status === 'completed' && effectiveAttempt.passed
                  ? `Тест зачтён${effectiveAttempt.score != null ? ` · ${effectiveAttempt.score}%` : ''}`
                  : (
                    <>
                      Последняя попытка: {effectiveAttempt.score != null ? `${effectiveAttempt.score}%` : '—'}
                      {effectiveAttempt.passed != null && (effectiveAttempt.passed ? ' · зачтено' : ' · не зачтено')}
                    </>
                  )}
            </div>
          )}
          {canRetake ? (
            <>
              <button
                type="button"
                className="btn btn-primary test-welcome-start"
                onClick={start}
                disabled={starting}
              >
                {starting
                  ? 'Запуск…'
                  : attempts.some((a) => a.status === 'in_progress') ? 'Продолжить' : 'Начать тест'}
              </button>
              {test.duration > 0 && (
                <p className="test-welcome-hint">После начала запустится таймер на {test.duration} мин. Незаполненные ответы будут засчитаны как пропуск.</p>
              )}
            </>
          ) : effectiveAttempt?.status === 'completed' && effectiveAttempt?.passed ? (
            <p className="test-welcome-hint">Тест зачтён. Повторное прохождение не требуется.</p>
          ) : null}
        </div>
      </TestPageShell>
    );
  }

  if (screen === 'results') {
    const resultTitle = result?.pendingReview
      ? 'На проверке'
      : result?.passed
        ? 'Тест зачтён'
        : 'Тест не зачтён';
    return (
      <TestPageShell backLabel="К тестам" onBack={handleBack} title={resultTitle}>
        <div className="test-results card card-pad">
          {result?.timedOut && (
            <div className="test-result-notice test-result-notice--warn">
              <Icon name="clock" size={16} />
              <span>Время вышло. Незаполненные ответы засчитаны как пропуск.</span>
            </div>
          )}
          {result?.pendingReview ? (
            <div className="test-result-status test-result-status--pending">
              <div className="test-result-status-icon"><Icon name="quiz" size={28} /></div>
              <p className="test-result-status-title">Ответы отправлены на проверку</p>
              <p className="test-result-status-text">Автор теста проверит открытые вопросы. Результат появится в списке тестов.</p>
            </div>
          ) : result?.score != null ? (
            <div className="test-result-status">
              <div className={`test-result-score${result.passed ? ' is-pass' : ' is-fail'}`}>{result.score}%</div>
              {result.correctCount != null && (
                <p className="test-result-meta">
                  Правильных ответов: {result.correctCount} из {result.totalGradable ?? questions.length}
                </p>
              )}
            </div>
          ) : null}
          {!preview && (
            <div className="test-result-actions">
              {result?.questions?.length > 0 && (
                <button type="button" className="btn btn-ghost" onClick={() => setScreen('breakdown')}>Посмотреть разбор</button>
              )}
              {test.articleId && (
                <button type="button" className="btn btn-ghost" onClick={() => handleBack('article')}>Вернуться к статье</button>
              )}
              {canRetake && !result?.pendingReview && (
                <button type="button" className="btn btn-primary" onClick={start}>Пройти повторно</button>
              )}
            </div>
          )}
          {preview && <button type="button" className="btn btn-primary" onClick={handleBack}>Закрыть превью</button>}
        </div>
      </TestPageShell>
    );
  }

  if (screen === 'breakdown' && result?.questions) {
    return (
      <TestPageShell
        backLabel="К результатам"
        onBack={() => setScreen('results')}
        title="Разбор"
        titleItalic="ответов"
      >
        <div className="test-breakdown">
          {result.questions.map((q, i) => {
            const ans = (result.answers || []).find((a) => a.questionId === q.id);
            const chosenText = ans?.chosenText
              || q.options?.find((o) => o.id === ans?.optionId)?.text;
            const correctText = ans?.correctText
              || q.options?.find((o) => o.isCorrect)?.text;
            const skipped = q.type === 'choice' && !ans?.optionId;
            return (
              <div className="card card-pad test-breakdown-item" key={q.id || i}>
                <div className="test-q-num">Вопрос {i + 1}</div>
                <h3 className="test-q-title">{q.text}</h3>
                {q.type === 'choice' ? (
                  <>
                    <p className={`test-breakdown-verdict${ans?.isCorrect ? ' ok' : skipped ? ' skip' : ' fail'}`}>
                      {skipped ? 'Пропуск' : ans?.isCorrect ? 'Верно' : 'Неверно'}
                    </p>
                    {chosenText ? (
                      <p className="test-breakdown-answer"><span>Ваш ответ:</span> {chosenText}</p>
                    ) : !skipped && (
                      <p className="test-breakdown-answer"><span>Ваш ответ:</span> —</p>
                    )}
                    {correctText && !ans?.isCorrect && (
                      <p className="test-breakdown-correct"><span>Правильный ответ:</span> {correctText}</p>
                    )}
                  </>
                ) : (
                  <>
                    <p className="test-breakdown-answer"><span>Ваш ответ:</span> {ans?.openText?.trim() || '—'}</p>
                    {result.pendingReview && (
                      <p className="test-breakdown-pending">Ожидает проверки автором</p>
                    )}
                  </>
                )}
              </div>
            );
          })}
        </div>
      </TestPageShell>
    );
  }

  const q = questions[current];
  const qk = qKey(q, current);
  const progress = questions.length ? Math.round(((current + 1) / questions.length) * 100) : 0;

  return (
    <TestPageShell
      backLabel={preview ? 'К редактору' : null}
      onBack={preview ? handleBack : null}
      title={test.title}
      subtitle={`Вопрос ${current + 1} из ${questions.length}`}
      actions={!preview && timeLeft != null ? (
        <div className={`test-timer${timeLeft <= 60 ? ' is-urgent' : ''}`}>
          <Icon name="clock" size={14} />
          <span>{formatTestTime(timeLeft)}</span>
        </div>
      ) : null}
      banner={preview ? (
        <div className="test-preview-banner">
          <Icon name="eye" size={14} /> Режим превью — ответы не сохраняются
        </div>
      ) : null}
    >
      <div className="test-take-layout">
        <aside className="card card-pad test-take-nav">
          <div className="test-take-nav-title">Вопросы</div>
          <div className="test-take-nav-list">
            {questions.map((item, i) => {
              const answered = isQuestionAnswered(item, i, answers);
              return (
                <button
                  key={qKey(item, i)}
                  type="button"
                  className={`test-take-nav-row${i === current ? ' active' : ''}${answered ? ' done' : ''}`}
                  onClick={() => setCurrent(i)}
                >
                  <span className="test-take-nav-row-top">
                    <span className="test-take-nav-label">Вопрос {i + 1}</span>
                    {answered && <Icon name="check" size={12} className="test-take-nav-check" />}
                  </span>
                  <span className="test-take-nav-preview">{truncate(item.text)}</span>
                </button>
              );
            })}
          </div>
          <button type="button" className="btn btn-primary test-take-finish" disabled={!allAnswered && !preview} onClick={() => finish()}>
            {preview ? 'Завершить превью' : 'Завершить тест'}
          </button>
        </aside>
        <div className="test-take-main card card-pad">
          <div className="test-progress"><div className="test-progress-bar" style={{ width: `${progress}%` }} /></div>
          <div className="test-q-num">Вопрос {current + 1}</div>
          <h2 className="test-q-title">{q.text || '—'}</h2>
          {q.type === 'choice' ? (
            <div className="test-choice-list">
              {(q.options || []).map((o, oi) => {
                const selected = isAnswerSelected(answers[qk], q, o, oi);
                return (
                  <label key={o.id || `opt-${oi}`} className={`test-choice-item${selected ? ' selected' : ''}`}>
                    <span className="test-choice-marker">{String.fromCharCode(65 + oi)}</span>
                    <input type="radio" name={`q-${qk}`} className="sr-only"
                      checked={selected}
                      onChange={() => setAnswers((a) => ({
                        ...a,
                        [qk]: q.id && o.id ? { optionId: o.id } : { optionIndex: oi },
                      }))} />
                    <span className="test-choice-text">{o.text || `Вариант ${oi + 1}`}</span>
                  </label>
                );
              })}
            </div>
          ) : (
            <textarea className="test-open-answer" rows={5} placeholder="Ваш ответ…"
              value={answers[qk]?.openText || ''}
              onChange={(e) => setAnswers((a) => ({ ...a, [qk]: { openText: e.target.value } }))} />
          )}
          <div className="test-take-actions">
            <button type="button" className="btn btn-ghost" disabled={current === 0} onClick={() => setCurrent((c) => c - 1)}>Назад</button>
            {current >= questions.length - 1 ? (
              <button type="button" className="btn btn-primary" disabled={!allAnswered && !preview} onClick={() => finish()}>
                {preview ? 'Завершить превью' : 'Завершить тест'}
              </button>
            ) : (
              <button type="button" className="btn btn-primary" onClick={() => setCurrent((c) => c + 1)}>Далее</button>
            )}
          </div>
        </div>
      </div>
    </TestPageShell>
  );
};

const TestReview = ({ testId, attemptId, onBack, backLabel = 'К тестам' }) => {
  const [items, setItems] = React.useState([]);
  const [test, setTest] = React.useState(null);
  const [allDone, setAllDone] = React.useState(false);
  const [grading, setGrading] = React.useState(null);

  const load = async () => {
    const [review, t] = await Promise.all([
      window.KB.getTestReview(testId),
      window.KB.getTest(testId),
    ]);
    const filtered = attemptId ? review.filter((i) => i.attemptId === attemptId) : review;
    setItems(filtered);
    setTest(t);
    return filtered;
  };

  React.useEffect(() => {
    setAllDone(false);
    load();
  }, [testId, attemptId]);

  const gradeAnswer = async (itemAttemptId, answerId, accepted, comment) => {
    const key = `${itemAttemptId}-${answerId}`;
    setGrading(key);
    try {
      const hadItems = items.length > 0;
      await window.KB.gradeTestAttempt(testId, itemAttemptId, [{
        answerId, accepted, comment: comment || '',
      }]);
      const next = await load();
      if (hadItems && !next.length) setAllDone(true);
    } catch (e) {
      await window.UI.alert({ title: 'Ошибка', message: e.message });
    } finally {
      setGrading(null);
    }
  };

  const ReviewAnswer = ({ item, answer: a }) => {
    const [comment, setComment] = React.useState(a.reviewComment || '');
    const isPending = !a.reviewStatus || a.reviewStatus === 'pending';
    const isAccepted = a.reviewStatus === 'accepted';
    const isRejected = a.reviewStatus === 'rejected';
    const busy = grading === `${item.attemptId}-${a.answerId}`;

    return (
      <div
        className={`test-review-answer${isAccepted ? ' is-accepted' : ''}${isRejected ? ' is-rejected' : ''}${isPending ? ' is-pending' : ''}`}
        key={a.answerId}
      >
        <div className="test-review-answer-top">
          <p className="test-q-title">{a.questionText}</p>
          {!isPending && (
            <span className={`test-review-verdict${isAccepted ? ' ok' : ' fail'}`}>
              <Icon name={isAccepted ? 'check' : 'x'} size={14} />
              {isAccepted ? 'Зачтено' : 'Не зачтено'}
            </span>
          )}
        </div>
        <div className="settings-readonly-field test-review-answer-text">{a.openText || '—'}</div>
        {isPending ? (
          <>
            <label className="settings-field test-review-comment">
              <span>Комментарий для сотрудника</span>
              <textarea rows={2} value={comment} onChange={(e) => setComment(e.target.value)} placeholder="Необязательно" />
            </label>
            <div className="test-review-actions">
              <button
                type="button"
                className={`btn btn-sm test-review-btn-pass${busy ? ' is-busy' : ''}`}
                disabled={Boolean(grading)}
                onClick={() => gradeAnswer(item.attemptId, a.answerId, true, comment)}
              >
                Зачтено
              </button>
              <button
                type="button"
                className={`btn btn-sm test-review-btn-fail${busy ? ' is-busy' : ''}`}
                disabled={Boolean(grading)}
                onClick={() => gradeAnswer(item.attemptId, a.answerId, false, comment)}
              >
                Не зачтено
              </button>
            </div>
          </>
        ) : a.reviewComment ? (
          <p className="test-review-given-comment"><span>Комментарий:</span> {a.reviewComment}</p>
        ) : null}
      </div>
    );
  };

  return (
    <TestPageShell
      backLabel={backLabel}
      onBack={onBack}
      title="Проверка"
      titleItalic={test?.title}
      subtitle="Открытые ответы"
    >
      {allDone ? (
        <div className="test-review-done card card-pad">
          <div className="test-result-status-icon"><Icon name="check" size={28} /></div>
          <p className="test-result-status-title">Проверка завершена</p>
          <p className="test-result-status-text">Все открытые ответы оценены. Сотрудник увидит результат в списке тестов.</p>
          <button type="button" className="btn btn-primary" onClick={onBack}>{backLabel}</button>
        </div>
      ) : !items.length ? (
        <div className="settings-info-box">Нет попыток, ожидающих проверки</div>
      ) : items.map((item) => {
        const total = item.openAnswers.length;
        const done = item.openAnswers.filter((a) => a.reviewStatus && a.reviewStatus !== 'pending').length;
        return (
          <div className="card card-pad test-review-card" key={item.attemptId}>
            <div className="test-review-head">
              <div className="test-review-person">
                <strong>{item.personName}</strong>
                {item.personRole && <span className="test-review-role">{item.personRole}</span>}
              </div>
              <span className={`test-review-progress${done === total ? ' is-complete' : ''}`}>
                Проверено {done} из {total}
              </span>
            </div>
            {item.openAnswers.map((a) => (
              <ReviewAnswer key={a.answerId} item={item} answer={a} />
            ))}
          </div>
        );
      })}
    </TestPageShell>
  );
};

const attemptStatusLabel = (status, passed) => {
  if (status === 'in_progress') return { label: 'В процессе', className: 'in-progress' };
  if (status === 'pending_review') return { label: 'На проверке', className: 'pending' };
  if (status === 'completed' && passed) return { label: 'Зачтено', className: 'passed' };
  if (status === 'completed') return { label: 'Не зачтено', className: 'failed' };
  return { label: '—', className: 'draft' };
};

const answerVerdictMeta = (a) => {
  const skipped = a.questionType === 'choice' && !a.chosenText && !a.openText?.trim();
  if (a.questionType === 'open') {
    if (a.reviewStatus === 'accepted') return { label: 'Зачтено', tone: 'ok', statusClass: 'passed' };
    if (a.reviewStatus === 'rejected') return { label: 'Не зачтено', tone: 'fail', statusClass: 'failed' };
    if (a.reviewStatus === 'pending') return { label: 'На проверке', tone: 'pending', statusClass: 'pending' };
    if (!a.openText?.trim()) return { label: 'Пропуск', tone: 'muted', statusClass: 'not-started' };
    return { label: 'Ожидает проверки', tone: 'pending', statusClass: 'pending' };
  }
  if (skipped) return { label: 'Пропуск', tone: 'muted', statusClass: 'not-started' };
  return a.isCorrect
    ? { label: 'Верно', tone: 'ok', statusClass: 'passed' }
    : { label: 'Неверно', tone: 'fail', statusClass: 'failed' };
};

const TestStatsAttemptDetail = ({ att, passing, attemptNum, isOpen, onToggle }) => {
  const st = attemptStatusLabel(att.status, att.passed);
  const when = att.startedAt ? new Date(att.startedAt).toLocaleString('ru', {
    day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit',
  }) : '—';
  const scoreLabel = att.status === 'pending_review'
    ? 'На проверке'
    : att.score != null ? `${att.score}%` : '—';

  return (
    <article className={`test-stats-attempt${isOpen ? ' is-open' : ''}`}>
      <button type="button" className="test-stats-attempt-row" onClick={onToggle}>
        <span className="test-stats-attempt-left">
          <Icon name={isOpen ? 'chevronDown' : 'chevronRight'} size={14} className="test-stats-chevron" />
          <span className="test-stats-attempt-num">№{attemptNum}</span>
          <span className={`test-user-status test-user-status--${st.className}`}>{st.label}</span>
        </span>
        <span className="test-stats-attempt-right">
          <span className="test-stats-attempt-when">{when}</span>
          {att.durationSec != null && (
            <span className="test-stats-attempt-dur">{formatDurationSec(att.durationSec)}</span>
          )}
          <span className="test-stats-attempt-score">{scoreLabel}</span>
        </span>
      </button>
      {isOpen && (
        <div className="test-stats-attempt-body">
          <div className="test-stats-answers">
            {att.answers.map((a, qi) => {
              const v = answerVerdictMeta(a);
              const answerText = a.questionType === 'open'
                ? (a.openText?.trim() || '—')
                : (a.chosenText || '—');
              return (
                <div key={a.questionId || qi} className="test-stats-answer-card">
                  <div className="test-stats-answer-head">
                    <span className="test-q-num">Вопрос {qi + 1}</span>
                    <span className={`test-user-status test-user-status--${v.statusClass}`}>{v.label}</span>
                  </div>
                  <p className="test-q-title test-stats-q-title">{a.questionText || '—'}</p>
                  <p className="test-breakdown-answer">
                    <span>Ответ</span>
                    {answerText}
                  </p>
                  {a.questionType === 'choice' && a.correctText && a.isCorrect === false && (
                    <p className="test-breakdown-correct">
                      <span>Правильный ответ</span>
                      {a.correctText}
                    </p>
                  )}
                  {a.reviewComment && (
                    <p className="test-stats-review-note">
                      <span>Комментарий</span>
                      {a.reviewComment}
                    </p>
                  )}
                </div>
              );
            })}
          </div>
          {att.score != null && (
            <div className="test-stats-attempt-summary">
              <span>Итог: <strong>{att.score}%</strong></span>
              <span>Проходной: <strong>{passing}%</strong></span>
            </div>
          )}
        </div>
      )}
    </article>
  );
};

const TestStats = ({ testId, onBack }) => {
  const [stats, setStats] = React.useState(null);
  const [test, setTest] = React.useState(null);
  const [expandedPersonId, setExpandedPersonId] = React.useState(null);
  const [personDetails, setPersonDetails] = React.useState({});
  const [loadingPersonId, setLoadingPersonId] = React.useState(null);
  const [openAttemptId, setOpenAttemptId] = React.useState(null);

  React.useEffect(() => {
    (async () => {
      setStats(await window.KB.getTestStats(testId));
      setTest(await window.KB.getTest(testId));
    })();
  }, [testId]);

  const togglePerson = async (personId) => {
    if (expandedPersonId === personId) {
      setExpandedPersonId(null);
      setOpenAttemptId(null);
      return;
    }
    setExpandedPersonId(personId);
    setOpenAttemptId(null);
    if (personDetails[personId]) return;
    setLoadingPersonId(personId);
    try {
      const data = await window.KB.getTestPersonStats(testId, personId);
      setPersonDetails((prev) => ({ ...prev, [personId]: data }));
    } catch (e) {
      await window.UI.alert({ title: 'Ошибка', message: e.message });
      setExpandedPersonId(null);
    } finally {
      setLoadingPersonId(null);
    }
  };

  const passing = personDetails[expandedPersonId]?.passing ?? test?.passing ?? 80;

  return (
    <TestPageShell
      backLabel="К тестам"
      onBack={onBack}
      title="Статистика"
      titleItalic={test?.title}
    >
      {stats?.problemQuestions?.length > 0 && (
        <div className="card card-pad test-stats-block">
          <h3 className="test-panel-title">Проблемные вопросы</h3>
          <p className="test-stats-hint">Вопросы с наибольшим числом ошибок.</p>
          <div className="test-problem-list">
            {stats.problemQuestions.map((q) => (
              <div key={q.id} className="test-problem-item">
                <span className="test-problem-text">{q.text}</span>
                <span className="tag tag-accent">{q.errorRate}% ошибок</span>
              </div>
            ))}
          </div>
        </div>
      )}
      <div className="card settings-table-card test-stats-table-card">
        <div className="test-stats-table-head">
          <h3 className="test-panel-title">Сотрудники</h3>
          <p className="test-stats-hint">Нажмите на строку — раскроется список попыток.</p>
        </div>
        <table className="settings-table test-stats-table">
          <thead>
            <tr>
              <th className="test-stats-col-toggle" aria-hidden="true" />
              <th>Сотрудник</th>
              <th>Должность</th>
              <th>Попыток</th>
              <th>Лучший</th>
              <th>Последняя дата</th>
              <th>Статус</th>
            </tr>
          </thead>
          <tbody>
            {(stats?.rows || []).length ? stats.rows.map((r) => {
              const isOpen = expandedPersonId === r.personId;
              const detail = personDetails[r.personId];
              const loading = loadingPersonId === r.personId;
              return (
                <React.Fragment key={r.personId}>
                  <tr
                    className={`test-stats-person-row${isOpen ? ' is-open' : ''}`}
                    onClick={() => togglePerson(r.personId)}
                  >
                    <td className="test-stats-col-toggle">
                      <Icon name={isOpen ? 'chevronDown' : 'chevronRight'} size={14} />
                    </td>
                    <td className="test-stats-person-name">{r.name}</td>
                    <td>{r.role || '—'}</td>
                    <td>{r.attempts}</td>
                    <td>{r.bestScore != null ? `${r.bestScore}%` : '—'}</td>
                    <td>{r.lastDate ? new Date(r.lastDate).toLocaleDateString('ru') : '—'}</td>
                    <td>
                      {r.passed
                        ? <span className="status status-published">Зачтено</span>
                        : <span className="status status-draft">Не зачтено</span>}
                    </td>
                  </tr>
                  {isOpen && (
                    <tr className="test-stats-expand-row">
                      <td colSpan={7}>
                        <div className="test-stats-expand-panel">
                          {loading && <div className="test-stats-loading">Загрузка попыток…</div>}
                          {!loading && detail && !detail.attempts.length && (
                            <div className="test-stats-loading">Нет попыток</div>
                          )}
                          {!loading && detail?.attempts?.map((att, idx) => (
                            <TestStatsAttemptDetail
                              key={att.id}
                              att={att}
                              passing={passing}
                              attemptNum={detail.attempts.length - idx}
                              isOpen={openAttemptId === att.id}
                              onToggle={(e) => {
                                e.stopPropagation();
                                setOpenAttemptId(openAttemptId === att.id ? null : att.id);
                              }}
                            />
                          ))}
                        </div>
                      </td>
                    </tr>
                  )}
                </React.Fragment>
              );
            }) : (
              <tr><td colSpan={7} className="text-3">Пока нет попыток</td></tr>
            )}
          </tbody>
        </table>
      </div>
    </TestPageShell>
  );
};

const TestReviewHub = ({ onBack, onOpenReview }) => {
  const [items, setItems] = React.useState([]);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    (async () => {
      setLoading(true);
      try {
        const data = await window.KB.getTestModerationOverview();
        setItems(data);
      } catch (e) {
        await window.UI.alert({ title: 'Ошибка', message: e.message });
      } finally {
        setLoading(false);
      }
    })();
  }, []);

  const totalPending = items.reduce((s, t) => s + (t.pendingCount || 0), 0);

  return (
    <TestPageShell backLabel="К тестам" onBack={onBack} title="Проверка" titleItalic="тестов">
      {loading ? (
        <div className="settings-info-box">Загрузка…</div>
      ) : !items.length ? (
        <div className="settings-info-box">Нет тестов, где вы автор или ответственный за статью.</div>
      ) : (
        <>
          {totalPending > 0 && (
            <div className="test-review-hub-summary">
              {totalPending} {totalPending === 1 ? 'попытка' : totalPending < 5 ? 'попытки' : 'попыток'} ожидают проверки
            </div>
          )}
          <div className="test-review-hub-list">
            {items.map((t) => (
              <article className="card card-pad test-review-hub-card" key={t.id}>
                <div className="test-review-hub-card-head">
                  <div>
                    <span className="test-card-id">{t.code || t.id}</span>
                    <h3 className="test-review-hub-title">{t.title}</h3>
                    <p className="test-review-hub-meta">
                      {t.questions || 0} вопросов · проходной {t.passing}% · {t.duration || 0} мин
                    </p>
                  </div>
                  {t.pendingCount > 0 && (
                    <span className="test-review-hub-pending-badge">{t.pendingCount} на проверке</span>
                  )}
                </div>
                {t.attempts?.length ? (
                  <div className="test-review-hub-table-wrap">
                    <table className="settings-table test-review-hub-table">
                      <thead>
                        <tr>
                          <th>Сотрудник</th>
                          <th>Результат</th>
                          <th>Время</th>
                          <th>Дата</th>
                          <th />
                        </tr>
                      </thead>
                      <tbody>
                        {t.attempts.map((a) => {
                          const st = attemptStatusLabel(a.status, a.passed);
                          return (
                            <tr key={a.id}>
                              <td className="test-review-hub-person">
                                <strong>{a.personName}</strong>
                                {a.personRole && <span className="test-review-hub-role">{a.personRole}</span>}
                              </td>
                              <td>
                                <span className={`test-user-status test-user-status--${st.className}`}>{st.label}</span>
                                {a.score != null && (
                                  <span className="test-review-hub-score">
                                    {a.status === 'pending_review' ? `~${a.score}%` : `${a.score}%`}
                                  </span>
                                )}
                              </td>
                              <td>{formatDurationSec(a.durationSec)}</td>
                              <td>{a.finishedAt ? new Date(a.finishedAt).toLocaleString('ru', { day: 'numeric', month: 'short', hour: '2-digit', minute: '2-digit' }) : '—'}</td>
                              <td className="test-review-hub-action">
                                {a.status === 'pending_review' ? (
                                  <button type="button" className="btn btn-primary btn-sm" onClick={() => onOpenReview(t.id, a.id)}>
                                    Проверить
                                  </button>
                                ) : null}
                              </td>
                            </tr>
                          );
                        })}
                      </tbody>
                    </table>
                  </div>
                ) : (
                  <p className="test-review-hub-empty">Попыток пока нет</p>
                )}
              </article>
            ))}
          </div>
        </>
      )}
    </TestPageShell>
  );
};

window.TestFlow = { TestEditor, TestTakeFlow, TestReview, TestStats, TestReviewHub };
