import { useEffect, useState } from 'react';
import { useNavigate, useParams, Link, useOutletContext } from 'react-router-dom';
import { api } from '../api';
import { formatTestAuthorLabel } from '../utils/formatUserName';

function fmtDt(iso) {
  if (!iso) {
    return '—';
  }
  try {
    return new Date(iso).toLocaleString('ru-RU', {
      dateStyle: 'short',
      timeStyle: 'short',
    });
  } catch {
    return '—';
  }
}

function newKey() {
  return globalThis.crypto?.randomUUID?.() ?? `k-${Date.now()}-${Math.random()}`;
}

function createEmptyQuestion() {
  return {
    key: newKey(),
    text: '',
    hasMultipleAnswers: false,
    options: [
      { key: newKey(), text: '', isCorrect: true },
      { key: newKey(), text: '', isCorrect: false },
    ],
  };
}

/**
 * @param {{ title: string, subtitle?: string, defaultOpen?: boolean, id?: string, children: import('react').ReactNode }} p
 */
function AccSection({ title, subtitle, defaultOpen, id, children }) {
  return (
    <div className="cabinet-brick">
      <details className="cabinet-disclosure" defaultOpen={defaultOpen} id={id || undefined}>
        <summary className="cabinet-disclosure__summary">
          <span className="cabinet-disclosure__summary-text">
            <span className="cabinet-disclosure__summary-title font-headline">{title}</span>
            {subtitle ? <span className="cabinet-disclosure__summary-sub">{subtitle}</span> : null}
          </span>
        </summary>
        <div className="cabinet-disclosure__body">{children}</div>
      </details>
    </div>
  );
}

function mapEditorToDraftQuestions(ed) {
  if (!ed?.questions?.length) {
    return [createEmptyQuestion()];
  }
  return ed.questions.map((q) => ({
    key: String(q.id),
    id: q.id,
    text: q.text ?? '',
    hasMultipleAnswers: !!q.hasMultipleAnswers,
    options: (q.options || []).map((o) => ({
      key: String(o.id),
      id: o.id,
      text: o.text ?? '',
      isCorrect: !!o.isCorrect,
    })),
  }));
}

export default function TestDetail() {
  const { id } = useParams();
  const nav = useNavigate();
  const { user, assignmentUi } = useOutletContext() || {};
  const [data, setData] = useState(null);
  /** V.8: сотрудник / не-автор — краткая карточка без редактора */
  const [taker, setTaker] = useState(null);
  const [chain, setChain] = useState(null);
  const [err, setErr] = useState(null);
  const [draftTitle, setDraftTitle] = useState('');
  const [draftDescription, setDraftDescription] = useState('');
  const [draftPassing, setDraftPassing] = useState('70');
  const [draftQuestions, setDraftQuestions] = useState(() => [createEmptyQuestion()]);
  const [draftStatus, setDraftStatus] = useState('');
  const [deactivateBusy, setDeactivateBusy] = useState(false);
  const [importPreview, setImportPreview] = useState(null);
  const [importErr, setImportErr] = useState(null);
  const [importBusy, setImportBusy] = useState(false);
  const [aiTestBusy, setAiTestBusy] = useState(false);
  const [aiQBusy, setAiQBusy] = useState(null);
  /** Параметры блока «Сгенерировать тест (ИИ)» (редизайн формы редактора) */
  const [aiGenTopic, setAiGenTopic] = useState('');
  const [aiQuestionsCount, setAiQuestionsCount] = useState(7);
  const [aiAnswersCount, setAiAnswersCount] = useState(3);
  const [assignSearch, setAssignSearch] = useState('');
  const [assignSearchApplied, setAssignSearchApplied] = useState('');
  const [assignDept, setAssignDept] = useState('__all__');
  const [assignClinic, setAssignClinic] = useState('all');
  const [assignPeople, setAssignPeople] = useState([]);
  const [assignDepts, setAssignDepts] = useState([]);
  const [assignSelected, setAssignSelected] = useState(() => new Set());
  const [assignMsg, setAssignMsg] = useState('');
  const [assignErr, setAssignErr] = useState(null);
  const [assignLoadBusy, setAssignLoadBusy] = useState(false);
  const [attemptsList, setAttemptsList] = useState(undefined);
  const [attemptsErr, setAttemptsErr] = useState(null);

  async function load() {
    setErr(null);
    setData(null);
    setTaker(null);
    setChain(null);
    try {
      const sum = await api(`/api/tests/${id}/summary`);
      if (!sum.isAuthor) {
        const c = await api(`/api/tests/${id}/chain-info`);
        setTaker({ summary: sum, chain: c });
        return;
      }
      const [v, c, ed] = await Promise.all([
        api(`/api/tests/${id}/versions`),
        api(`/api/tests/${id}/chain-info`),
        api(`/api/tests/${id}/editor`),
      ]);
      setData(v);
      setChain(c);
      if (ed?.test) {
        setDraftTitle(ed.test.title || '');
        setAiGenTopic((ed.test.title || '').trim());
        setDraftDescription(ed.test.description || '');
        const th = ed.test.passingThreshold;
        setDraftPassing(
          th !== undefined && th !== null && String(th) !== '' ? String(th) : '70'
        );
        setDraftQuestions(mapEditorToDraftQuestions(ed));
      }
    } catch (e) {
      if (e.status === 401) {
        nav('/login');
        return;
      }
      setErr(e.message);
    }
  }

  useEffect(() => {
    load();
  }, [id, nav]);

  useEffect(() => {
    if (!data) {
      setAttemptsList(undefined);
      setAttemptsErr(null);
      return;
    }
    let cancelled = false;
    setAttemptsErr(null);
    (async () => {
      try {
        const r = await api(`/api/tests/${id}/attempts`);
        if (!cancelled) {
          setAttemptsList(r.attempts || []);
        }
      } catch (e) {
        if (!cancelled) {
          if (e.status === 403 || e.status === 404) {
            setAttemptsList(null);
          } else {
            setAttemptsErr(e.message);
            setAttemptsList(null);
          }
        }
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [data, id]);

  useEffect(() => {
    const t = setTimeout(() => setAssignSearchApplied(assignSearch), 400);
    return () => clearTimeout(t);
  }, [assignSearch]);

  useEffect(() => {
    if (!assignmentUi || !data) {
      return;
    }
    let cancelled = false;
    (async () => {
      setAssignErr(null);
      setAssignLoadBusy(true);
      try {
        const params = new URLSearchParams();
        if (assignSearchApplied.trim()) {
          params.set('q', assignSearchApplied.trim());
        }
        if (assignDept && assignDept !== '__all__') {
          params.set('department', assignDept);
        }
        params.set('clinic', assignClinic);
        const r = await api(`/api/auth/dev/assignment-directory?${params.toString()}`);
        if (cancelled) {
          return;
        }
        setAssignPeople(r.people || []);
        setAssignDepts(r.departments || []);
        setAssignSelected(new Set());
      } catch (e) {
        if (!cancelled) {
          setAssignErr(e.message);
        }
      } finally {
        if (!cancelled) {
          setAssignLoadBusy(false);
        }
      }
    })();
    return () => {
      cancelled = true;
    };
  }, [assignmentUi, data, id, assignSearchApplied, assignDept, assignClinic]);

  function assignPersonKey(p) {
    return `${p.staffId ?? '—'}|${p.clinicUserId ?? '—'}`;
  }

  function toggleAssignPerson(p) {
    const k = assignPersonKey(p);
    setAssignSelected((prev) => {
      const next = new Set(prev);
      if (next.has(k)) {
        next.delete(k);
      } else {
        next.add(k);
      }
      return next;
    });
  }

  function selectAllVisible() {
    setAssignSelected((prev) => {
      const next = new Set(prev);
      for (const p of assignPeople) {
        next.add(assignPersonKey(p));
      }
      return next;
    });
  }

  async function postAssign() {
    const selectedRows = assignPeople.filter((p) =>
      assignSelected.has(assignPersonKey(p))
    );
    if (selectedRows.length === 0) {
      return;
    }
    setAssignMsg('');
    setAssignErr(null);
    const userIds = [];
    const staffIds = [];
    for (const p of selectedRows) {
      if (p.clinicUserId) {
        userIds.push(p.clinicUserId);
      } else if (p.staffId != null) {
        staffIds.push(p.staffId);
      }
    }
    if (userIds.length === 0 && staffIds.length === 0) {
      setAssignErr('Не выбраны сотрудники с валидным staff_id.');
      return;
    }
    try {
      const out = await api(`/api/tests/${id}/assign`, {
        method: 'POST',
        body: JSON.stringify({ userIds, staffIds }),
      });
      setAssignMsg(
        out.count != null
          ? `Назначено: ${out.count} сотр.`
          : 'Назначение сохранено.'
      );
      setAssignSelected(new Set());
    } catch (e) {
      setAssignErr(e.message);
    }
  }

  async function saveDraft() {
    setDraftStatus('…');
    try {
      const questions = draftQuestions.map((q, i) => ({
        text: (q.text || '').trim() || 'Вопрос',
        question_order: i + 1,
        hasMultipleAnswers: q.hasMultipleAnswers,
        options: q.options.map((o, j) => ({
          text: (o.text || '').trim() || 'Вариант',
          isCorrect: o.isCorrect,
          option_order: j + 1,
        })),
      }));
      const out = await api(`/api/tests/${id}/draft`, {
        method: 'POST',
        body: JSON.stringify({
          title: (draftTitle || '').trim() || 'Без названия',
          description: (draftDescription || '').trim() || null,
          passingThreshold: (() => {
            const n = Number(draftPassing);
            return Number.isFinite(n) ? n : undefined;
          })(),
          questions,
        }),
      });
      setDraftStatus(out.forked ? 'Сохранено как новая версия' : 'Сохранено');
      load();
    } catch (e) {
      setDraftStatus(e.message);
    }
  }

  function normalizeGeneratedQuestionOptions(q, targetCount) {
    const n = Math.min(12, Math.max(2, targetCount));
    const raw = (q?.options || []).map((o) => ({
      key: newKey(),
      text: (o.text || '').trim() || 'Вариант',
      isCorrect: !!o.isCorrect,
    }));
    const out = raw.slice(0, n);
    while (out.length < n) {
      out.push({ key: newKey(), text: '', isCorrect: false });
    }
    if (!out.some((o) => o.isCorrect)) {
      out[0] = { ...out[0], isCorrect: true };
    }
    return out;
  }

  async function runAiGenerateTest() {
    if (aiTestBusy || !id) {
      return;
    }
    const nQ = Math.min(30, Math.max(1, Math.floor(Number(aiQuestionsCount)) || 7));
    const nA = Math.min(8, Math.max(2, Math.floor(Number(aiAnswersCount)) || 3));
    const topic = (aiGenTopic || draftTitle || '').trim() || 'Тест';
    setDraftStatus('');
    setAiTestBusy(true);
    try {
      const shape = Array.from({ length: nQ }, () => ({
        optionsCount: nA,
        hasMultipleAnswers: false,
      }));
      const out = await api(`/api/tests/${id}/ai/generate-test`, {
        method: 'POST',
        body: JSON.stringify({
          testTitle: topic,
          testDescription: draftDescription,
          shape,
        }),
      });
      if (out.draft) {
        setDraftTitle((out.draft.title || '').trim() || 'Без названия');
        setDraftDescription(
          out.draft.description != null && String(out.draft.description).trim()
            ? String(out.draft.description).trim()
            : ''
        );
        const qs = (out.draft.questions || []).map((q) => ({
          key: newKey(),
          text: (q.text || '').trim() || 'Вопрос',
          hasMultipleAnswers: !!q.hasMultipleAnswers,
          options: normalizeGeneratedQuestionOptions(q, nA),
        }));
        if (qs.length) {
          setDraftQuestions(qs);
        }
        setDraftStatus('Тексты и верные варианты сгенерированы по сетке. Проверьте и сохраните.');
      }
    } catch (e) {
      setDraftStatus(e.message || 'Ошибка');
    } finally {
      setAiTestBusy(false);
    }
  }

  async function runAiGenerateQuestion(qi) {
    if (aiQBusy != null || !id) {
      return;
    }
    setDraftStatus('');
    const q = draftQuestions[qi];
    if (!q) {
      return;
    }
    setAiQBusy(qi);
    try {
      const out = await api(`/api/tests/${id}/ai/generate-question`, {
        method: 'POST',
        body: JSON.stringify({
          testTitle: draftTitle,
          testDescription: draftDescription,
          questionText: (q.text || '').trim(),
          optionsCount: Math.max(2, Math.min(12, q.options?.length || 2)),
          hasMultipleAnswers: q.hasMultipleAnswers,
        }),
      });
      if (out.mode === 'rephrase' && out.text) {
        setDraftQuestions((prev) =>
          prev.map((row, i) => (i === qi ? { ...row, text: out.text } : row))
        );
        setDraftStatus('Вопрос переформулирован. При необходимости сохраните.');
      } else if (out.mode === 'full') {
        setDraftQuestions((prev) =>
          prev.map((row, i) =>
            i === qi
              ? {
                  ...row,
                  key: newKey(),
                  text: (out.text || '').trim() || 'Вопрос',
                  hasMultipleAnswers: !!out.hasMultipleAnswers,
                  options: (out.options || []).map((o) => ({
                    key: newKey(),
                    text: (o.text || '').trim() || 'Вариант',
                    isCorrect: !!o.isCorrect,
                  })),
                }
              : row
          )
        );
        setDraftStatus('Вопрос и варианты сгенерированы. Проверьте и сохраните.');
      }
    } catch (e) {
      setDraftStatus(e.message || 'Ошибка');
    } finally {
      setAiQBusy(null);
    }
  }

  async function onImportFile(e) {
    const f = e.target.files?.[0];
    e.target.value = '';
    if (!f) {
      return;
    }
    setImportErr(null);
    setImportPreview(null);
    setImportBusy(true);
    try {
      const fd = new FormData();
      fd.append('file', f);
      const out = await api('/api/tests/import/document', {
        method: 'POST',
        body: fd,
      });
      setImportPreview(out);
    } catch (er) {
      setImportErr(er.message);
    } finally {
      setImportBusy(false);
    }
  }

  function applyExtractedToQuestion() {
    const t = importPreview?.extractedText;
    if (!t) {
      return;
    }
    const chunk = t.length > 2000 ? `${t.slice(0, 2000)}…` : t;
    setDraftQuestions((prev) => {
      if (!prev.length) {
        const q = createEmptyQuestion();
        q.text = chunk;
        return [q];
      }
      const next = [...prev];
      next[0] = { ...next[0], text: chunk };
      return next;
    });
  }

  function applyGeneratedDraft() {
    const d = importPreview?.generation?.draft;
    if (!d) {
      return;
    }
    setDraftTitle((d.title || '').trim() || 'Без названия');
    setAiGenTopic((d.title || '').trim());
    setDraftDescription(
      d.description != null && String(d.description).trim() ? String(d.description).trim() : ''
    );
    const qs = (d.questions || []).map((q) => {
      const opts = (q.options || []).map((o) => ({
        key: newKey(),
        text: (o.text || '').trim() || 'Вариант',
        isCorrect: !!o.isCorrect,
      }));
      if (opts.length < 2) {
        return {
          key: newKey(),
          text: (q.text || '').trim() || 'Вопрос',
          hasMultipleAnswers: !!q.hasMultipleAnswers,
          options: [
            { key: newKey(), text: 'Вариант 1', isCorrect: true },
            { key: newKey(), text: 'Вариант 2', isCorrect: false },
          ],
        };
      }
      return {
        key: newKey(),
        text: (q.text || '').trim() || 'Вопрос',
        hasMultipleAnswers: !!q.hasMultipleAnswers,
        options: opts,
      };
    });
    setDraftQuestions(qs.length ? qs : [createEmptyQuestion()]);
    setDraftStatus('Черновик из LLM перенесён в редактор. Сохраните при необходимости.');
  }

  function addQuestion() {
    setDraftQuestions((prev) => [...prev, createEmptyQuestion()]);
  }

  function removeQuestion(index) {
    setDraftQuestions((prev) => prev.filter((_, i) => i !== index));
  }

  function addOption(qIndex) {
    setDraftQuestions((prev) => {
      const next = prev.map((q, i) =>
        i === qIndex
          ? {
              ...q,
              options: [...q.options, { key: newKey(), text: '', isCorrect: false }],
            }
          : q
      );
      return next;
    });
  }

  function removeOption(qIndex, oIndex) {
    setDraftQuestions((prev) => {
      const next = prev.map((q, i) => {
        if (i !== qIndex) {
          return q;
        }
        if (q.options.length <= 1) {
          return q;
        }
        return { ...q, options: q.options.filter((_, j) => j !== oIndex) };
      });
      return next;
    });
  }

  async function activateVersion(vid) {
    if (!window.confirm('Сделать эту версию активной? Новые попытки пойдут по выбранной версии.')) {
      return;
    }
    try {
      await api(`/api/tests/${id}/versions/${vid}/activate`, {
        method: 'POST',
        body: JSON.stringify({}),
      });
      load();
    } catch (e) {
      setErr(e.message);
    }
  }

  async function setChainVisible(chainActive) {
    const act = chainActive ? 'снова показывать' : 'скрывать';
    if (!window.confirm(`Цепочка будет ${act} в общем списке тестов. Продолжить?`)) {
      return;
    }
    setDeactivateBusy(true);
    setErr(null);
    try {
      await api(`/api/tests/${id}`, {
        method: 'PATCH',
        body: JSON.stringify({ chainActive }),
      });
      await load();
    } catch (e) {
      setErr(e.message);
    } finally {
      setDeactivateBusy(false);
    }
  }

  if (err) {
    return <p className="error-text">{err}</p>;
  }
  if (!data && !taker) {
    return <p className="text-muted">Загрузка…</p>;
  }

  if (taker) {
    const { test: t, hasActiveVersion } = taker.summary;
    const title = t?.title || 'Тест';
    return (
      <div>
        <p className="link-back">
          <Link to="/tests">← к списку</Link>
        </p>
        <h1
          className="font-headline"
          style={{ fontSize: '1.35rem', marginTop: 0, marginBottom: '0.35rem' }}
        >
          {title}
        </h1>
        <p className="text-muted" style={{ marginTop: 0, marginBottom: '0.4rem', fontSize: 14 }}>
          {formatTestAuthorLabel(user, t?.createdBy, t?.authorFullName)}
        </p>
        {t?.description && (
          <p className="text-muted" style={{ marginTop: 0, marginBottom: '1rem' }}>
            {t.description}
          </p>
        )}
        <p className="text-muted" style={{ marginTop: 0, marginBottom: '1rem' }}>
          Порог для зачёта: <strong>{t?.passingThreshold ?? '—'}%</strong>
        </p>
        {!hasActiveVersion && (
          <div className="callout callout--warning" role="status">
            Активная версия недоступна. Обратитесь к автору теста.
          </div>
        )}
      </div>
    );
  }

  const { test, versions, hasAttempts } = data;
  const title = test?.title || 'Тест';
  const assignSelectedInList = assignPeople.filter((p) =>
    assignSelected.has(assignPersonKey(p))
  );

  return (
    <div className="test-detail-page test-detail-page--with-fixed-actions">
      <div className="cabinet-brick cabinet-brick--hero">
        <p className="link-back" style={{ marginTop: 0 }}>
          <Link to="/tests">← к списку</Link>
        </p>

        <h1
          className="font-headline"
          style={{ fontSize: '1.35rem', marginTop: 0, marginBottom: '0.35rem' }}
        >
          {title}
        </h1>
        <p className="text-muted" style={{ marginTop: 0, marginBottom: '0.5rem', fontSize: 14 }}>
          {formatTestAuthorLabel(user, test?.createdBy, test?.authorFullName)}
        </p>
        <p className="muted" style={{ marginBottom: 0 }}>
          Обновлён: {fmtDt(test?.updatedAt || test?.createdAt)}
          {test?.chainActive === false && (
            <span className="error-text" style={{ marginLeft: '0.75rem' }}>
              · скрыт из списка
            </span>
          )}
        </p>

        {test?.chainActive === false && (
          <div className="callout callout--warning" role="status" style={{ marginTop: '0.75rem' }}>
            Скрыт из общего списка.
          </div>
        )}

        {chain?.hasAnyAttempt && (
          <div className="callout callout--warning" role="status" style={{ marginTop: '0.75rem' }}>
            При сохранении будет создана новая версия теста.
          </div>
        )}
      </div>

      <AccSection
        title="О тесте"
        subtitle="Название, описание и порог зачёта"
        defaultOpen
      >
        <div className="draft-block">
          <label className="form-label" htmlFor="draft-title">
            Название
          </label>
          <input
            id="draft-title"
            className="form-input"
            value={draftTitle}
            onChange={(e) => setDraftTitle(e.target.value)}
          />
          <label className="form-label" htmlFor="draft-desc" style={{ marginTop: '0.75rem' }}>
            Описание
          </label>
          <textarea
            id="draft-desc"
            className="form-input"
            rows={2}
            value={draftDescription}
            onChange={(e) => setDraftDescription(e.target.value)}
          />
          <label className="form-label" htmlFor="draft-pass" style={{ marginTop: '0.75rem' }}>
            Порог зачёта, %
          </label>
          <input
            id="draft-pass"
            type="number"
            className="form-input"
            min={0}
            max={100}
            inputMode="numeric"
            value={draftPassing}
            onChange={(e) => setDraftPassing(e.target.value)}
            style={{ maxWidth: 120 }}
          />
        </div>
      </AccSection>

      <AccSection
        title="Вопросы"
        subtitle="Тексты, варианты и при необходимости загрузка из файла"
        defaultOpen
      >
        <div className="draft-block">
          <div className="test-detail-ai-panel" aria-label="Параметры генерации теста с ИИ">
            <p
              className="font-headline"
              style={{ fontSize: '0.95rem', marginTop: 0, marginBottom: '0.65rem' }}
            >
              Генерация сетки вопросов (ИИ)
            </p>
            <label className="form-label" htmlFor="ai-gen-topic">
              Тема
            </label>
            <input
              id="ai-gen-topic"
              className="form-input"
              value={aiGenTopic}
              onChange={(e) => setAiGenTopic(e.target.value)}
              placeholder="По умолчанию — как в «Название»; при необходимости уточните"
            />
            <div
              className="inline-actions"
              style={{ marginTop: '0.75rem', flexWrap: 'wrap', alignItems: 'flex-end', gap: '0.9rem' }}
            >
              <div>
                <label className="form-label" htmlFor="ai-n-questions" style={{ display: 'block' }}>
                  Вопросов
                </label>
                <input
                  id="ai-n-questions"
                  type="number"
                  className="form-input"
                  min={1}
                  max={30}
                  inputMode="numeric"
                  value={aiQuestionsCount}
                  onChange={(e) => {
                    const n = parseInt(e.target.value, 10);
                    if (!Number.isFinite(n) || n < 1) {
                      return;
                    }
                    setAiQuestionsCount(Math.min(30, Math.max(1, n)));
                  }}
                  style={{ maxWidth: 90 }}
                />
              </div>
              <div>
                <label className="form-label" htmlFor="ai-n-answers" style={{ display: 'block' }}>
                  Вариантов
                </label>
                <input
                  id="ai-n-answers"
                  type="number"
                  className="form-input"
                  min={2}
                  max={8}
                  inputMode="numeric"
                  value={aiAnswersCount}
                  onChange={(e) => {
                    const n = parseInt(e.target.value, 10);
                    if (!Number.isFinite(n) || n < 2) {
                      return;
                    }
                    setAiAnswersCount(Math.min(8, Math.max(2, n)));
                  }}
                  style={{ maxWidth: 90 }}
                />
              </div>
              <button
                type="button"
                className="btn btn-ghost"
                style={{ marginTop: '0.1rem' }}
                disabled={aiTestBusy}
                onClick={runAiGenerateTest}
              >
                {aiTestBusy ? 'Генерация…' : 'Сгенерировать тест (ИИ)'}
              </button>
            </div>
          </div>

        {draftQuestions.map((q, qi) => (
          <div
            key={q.key}
            className={`question-editor-block${qi === 0 ? ' question-editor-block--first' : ''}`}
          >
            <div className="question-editor-block__header">
              <label
                className="form-label question-editor-block__title"
                htmlFor={`qtext-${q.key}`}
              >
                Вопрос {qi + 1}
              </label>
              <button
                type="button"
                className="btn btn-ghost btn--sm question-editor-block__ai-btn"
                disabled={aiQBusy != null}
                onClick={() => runAiGenerateQuestion(qi)}
              >
                {aiQBusy === qi ? '…' : 'Сгенерировать вопрос (ИИ)'}
              </button>
            </div>
            <textarea
              id={`qtext-${q.key}`}
              className="form-input"
              rows={2}
              value={q.text}
              onChange={(e) => {
                const v = e.target.value;
                setDraftQuestions((prev) =>
                  prev.map((row, i) => (i === qi ? { ...row, text: v } : row))
                );
              }}
            />
            <label
              className="form-label"
              style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 8 }}
            >
              <input
                type="checkbox"
                checked={q.hasMultipleAnswers}
                onChange={(e) => {
                  const v = e.target.checked;
                  setDraftQuestions((prev) =>
                    prev.map((row, i) => (i === qi ? { ...row, hasMultipleAnswers: v } : row))
                  );
                }}
              />
              Несколько верных ответов
            </label>
            <p className="muted" style={{ marginBottom: 6, fontSize: 13 }}>
              Варианты
            </p>
            <ul className="question-options-list" style={{ listStyle: 'none', padding: 0, margin: 0 }}>
              {q.options.map((o, oi) => (
                <li key={o.key} className="question-option-row">
                  <input
                    key={`${o.key}-${q.hasMultipleAnswers ? 'm' : 's'}`}
                    className="question-option-row__mark"
                    type={q.hasMultipleAnswers ? 'checkbox' : 'radio'}
                    name={q.hasMultipleAnswers ? undefined : `q-${q.key}-correct`}
                    checked={o.isCorrect}
                    onChange={(e) => {
                      const v = e.target.checked;
                      setDraftQuestions((prev) =>
                        prev.map((row, i) => {
                          if (i !== qi) {
                            return row;
                          }
                          if (!row.hasMultipleAnswers && v) {
                            return {
                              ...row,
                              options: row.options.map((op, j) => ({
                                ...op,
                                isCorrect: j === oi,
                              })),
                            };
                          }
                          const options = row.options.map((op, j) =>
                            j === oi ? { ...op, isCorrect: v } : op
                          );
                          return { ...row, options };
                        })
                      );
                    }}
                    title="Верный ответ"
                    aria-label="Пометить как верный"
                  />
                  <input
                    className="form-input question-option-row__text"
                    value={o.text}
                    onChange={(e) => {
                      const v = e.target.value;
                      setDraftQuestions((prev) =>
                        prev.map((row, i) => {
                          if (i !== qi) {
                            return row;
                          }
                          return {
                            ...row,
                            options: row.options.map((op, j) => (j === oi ? { ...op, text: v } : op)),
                          };
                        })
                      );
                    }}
                    placeholder="Текст варианта"
                  />
                  {q.options.length > 1 && (
                    <button
                      type="button"
                      className="question-option-remove"
                      onClick={() => removeOption(qi, oi)}
                      title="Удалить вариант"
                      aria-label="Удалить вариант"
                    >
                      <span className="material-symbols-outlined" aria-hidden>
                        close
                      </span>
                    </button>
                  )}
                </li>
              ))}
            </ul>
            <div className="question-editor__footer">
              <button
                type="button"
                className="btn btn-ghost btn--sm"
                onClick={() => addOption(qi)}
              >
                + вариант
              </button>
              {draftQuestions.length > 1 && (
                <button
                  type="button"
                  className="btn btn-ghost btn--sm"
                  onClick={() => removeQuestion(qi)}
                >
                  Удалить вопрос
                </button>
              )}
            </div>
          </div>
        ))}
        <div className="test-detail-add-question">
          <button
            type="button"
            className="btn btn-ghost question-editor__add-question"
            onClick={addQuestion}
          >
            + вопрос
          </button>
        </div>

        <div className="test-detail-subsection test-detail-subsection--import">
          <h3 className="test-detail-subsection__title">Документ в вопросы</h3>
          <p className="muted test-detail-hint" style={{ marginTop: 0 }}>
            PDF, Word или текст — вставьте в черновик вопросов.
          </p>
          <div
            className="import-file-row import-file-row--block"
            style={{ marginBottom: importPreview || importErr ? '0.5rem' : 0, marginTop: 0 }}
          >
            <input
              id="import-test-file"
              className="import-file-input"
              type="file"
              accept=".pdf,.docx,.txt,.md,application/pdf"
              onChange={onImportFile}
              disabled={importBusy}
              aria-label="Выбрать файл для импорта"
            />
            <label
              htmlFor="import-test-file"
              className={`btn btn-ghost import-file-label${importBusy ? ' is-busy' : ''}`}
              style={importBusy ? { pointerEvents: 'none' } : undefined}
            >
              {importBusy ? 'Обработка…' : 'Выбрать файл'}
            </label>
          </div>
          {importErr && (
            <p className="error-text" role="alert">
              {importErr}
            </p>
          )}
          {importPreview && (
            <div className="draft-block" style={{ marginTop: '0.65rem', marginBottom: 0 }}>
              <p className="muted" style={{ marginTop: 0, marginBottom: 0 }}>
                {importPreview.generation?.message}
              </p>
              {importPreview.generation?.textPreview && !importPreview.generation?.available && (
                <pre
                  className="form-input"
                  style={{
                    maxHeight: 180,
                    overflow: 'auto',
                    fontSize: 13,
                    marginTop: 8,
                    whiteSpace: 'pre-wrap',
                  }}
                >
                  {importPreview.generation.textPreview}
                  {importPreview.textLength > 4000 ? '…' : ''}
                </pre>
              )}
              {importPreview.generation?.draft && (
                <div className="inline-actions" style={{ marginTop: 8, marginBottom: 0 }}>
                  <button
                    type="button"
                    className="btn btn-ghost"
                    onClick={applyGeneratedDraft}
                  >
                    Применить сгенерированный черновик
                  </button>
                </div>
              )}
              {importPreview.extractedText && (
                <div className="inline-actions" style={{ marginTop: 8, marginBottom: 0 }}>
                  <button
                    type="button"
                    className="btn btn-ghost"
                    onClick={applyExtractedToQuestion}
                  >
                    Вставить в первый вопрос (до 2000 симв.)
                  </button>
                </div>
              )}
            </div>
          )}
        </div>
        </div>
      </AccSection>

      <div className="cabinet-brick editor-actions-flow" style={{ marginTop: 0 }}>
        <div
          className="actions-bar"
          style={{ marginBottom: draftStatus ? '0.35rem' : 0 }}
        >
          <button type="button" className="btn btn-primary" onClick={saveDraft}>
            Сохранить черновик
          </button>
          <Link to="/tests" className="btn btn-ghost">
            К списку
          </Link>
        </div>
        {draftStatus && (
          <p className="muted editor-actions-status--flow" style={{ marginTop: 0, marginBottom: 0 }}>
            {draftStatus}
          </p>
        )}
      </div>
      <AccSection
        title="История"
        subtitle="Версии теста и кто проходил"
        defaultOpen={false}
      >
        <div className="test-detail-subsection test-detail-subsection--tight">
          <h3 className="test-detail-subsection__title">Версии</h3>
          <ul className="version-card-list" aria-label="Список версий теста">
            {versions.map((r) => (
              <li key={r.id} className="surface-card version-card-list__item">
                <div className="version-card-list__row">
                  <div className="version-card-list__main">
                    <div className="version-card-list__title-line">
                      <span className="font-headline" style={{ fontSize: '1rem' }}>
                        v{r.version}
                      </span>
                      {r.is_active && (
                        <span className="code-inline" style={{ fontSize: '0.7rem' }}>
                          текущая
                        </span>
                      )}
                    </div>
                    <p className="muted mono" style={{ margin: '0.4rem 0 0', fontSize: '0.8rem' }}>
                      {fmtDt(r.created_at)}
                    </p>
                    <p className="muted" style={{ margin: '0.2rem 0 0', fontSize: '0.8rem' }}>
                      Активна: {r.is_active ? 'да' : 'нет'}
                    </p>
                  </div>
                  {!r.is_active && (
                    <button
                      type="button"
                      className="btn btn-ghost btn--sm version-card-list__action"
                      onClick={() => activateVersion(r.id)}
                    >
                      Сделать активной
                    </button>
                  )}
                </div>
              </li>
            ))}
          </ul>
        </div>
        {attemptsList !== undefined && (
          <div className="test-detail-subsection">
            <h3 className="test-detail-subsection__title">Прохождения</h3>
            {attemptsList == null && (
              <p
                className={attemptsErr ? 'error-text' : 'text-muted'}
                style={{ margin: 0 }}
                role={attemptsErr ? 'alert' : undefined}
              >
                {attemptsErr || 'Список прогонов сейчас недоступен.'}
              </p>
            )}
            {attemptsList && attemptsList.length > 0 && (
              <ul className="attempts-card-list" aria-label="Список прогонов">
                {attemptsList.map((a) => {
                  const when = a.completedAt
                    ? fmtDt(a.completedAt)
                    : a.startedAt
                      ? fmtDt(a.startedAt)
                      : '—';
                  const result =
                    a.status === 'completed' && a.totalQuestions != null ? (
                      <>
                        {a.correctCount}/{a.totalQuestions}
                        {a.passed ? ' · зачёт' : ' · незачёт'}
                      </>
                    ) : (
                      a.status
                    );
                  return (
                    <li key={a.id} className="surface-card attempts-card-list__item">
                      <div className="attempts-card-list__row">
                        <div className="attempts-card-list__main">
                          <p
                            className="muted attempts-card-list__when mono"
                            style={{ margin: 0, fontSize: '0.8rem' }}
                          >
                            {when}
                          </p>
                          <p
                            className="attempts-card-list__who"
                            style={{ margin: '0.35rem 0 0', fontWeight: 600 }}
                          >
                            {a.attempterName || '—'}
                            {a.attempterLogin && (
                              <span className="code-inline" style={{ fontSize: 11, marginLeft: 6 }}>
                                {a.attempterLogin}
                              </span>
                            )}
                          </p>
                          <p className="muted" style={{ margin: '0.25rem 0 0', fontSize: '0.85rem' }}>
                            v{a.testVersion} ·{' '}
                            <span className="attempts-card-list__result">{result}</span>
                          </p>
                        </div>
                        {a.status === 'completed' && (
                          <Link
                            to={`/tests/${id}/attempts/${a.id}/review`}
                            className="btn btn-ghost btn--sm attempts-card-list__action"
                          >
                            Разбор
                          </Link>
                        )}
                      </div>
                    </li>
                  );
                })}
              </ul>
            )}
            {attemptsList && attemptsList.length === 0 && (
              <p className="text-muted" style={{ margin: 0 }}>
                Пока нет зарегистрированных прогонов.
              </p>
            )}
          </div>
        )}
      </AccSection>

      <AccSection
        title="Показ в каталоге"
        subtitle={
          assignmentUi && data
            ? 'Видимость в списке и выдача сотрудникам'
            : 'Показать или скрыть в общем списке'
        }
        defaultOpen={false}
      >
        <div className="test-detail-subsection test-detail-subsection--tight">
          <h3 className="test-detail-subsection__title">Видимость</h3>
          <p className="test-detail-hint" style={{ marginTop: 0 }}>
            Скрытые тесты в общем списке не показываются; ссылку на тест по-прежнему можно открыть.
          </p>
          <div className="publication-visibility__actions" style={{ marginTop: 4 }}>
            {test?.chainActive !== false ? (
              <button
                type="button"
                className="btn btn-ghost"
                disabled={deactivateBusy}
                onClick={() => setChainVisible(false)}
              >
                Скрыть из списка
              </button>
            ) : (
              <button
                type="button"
                className="btn btn-ghost"
                disabled={deactivateBusy}
                onClick={() => setChainVisible(true)}
              >
                Снова показать в списке
              </button>
            )}
          </div>
        </div>
        {assignmentUi && data && (
          <div className="test-detail-subsection">
            <h3 className="test-detail-subsection__title">Кому выдать</h3>
            <p className="test-detail-hint" style={{ marginTop: 0 }}>
              Список с учётом поиска и фильтров; можно отметить всех на экране.
            </p>
            {assignErr && (
              <p className="error-text" role="alert" style={{ marginTop: 0, marginBottom: 8 }}>
                {assignErr}
              </p>
            )}
            <div className="assign-toolbar">
              <input
                type="search"
                className="form-input assign-toolbar__search"
                placeholder="Поиск: ФИО, логин"
                value={assignSearch}
                onChange={(e) => setAssignSearch(e.target.value)}
                aria-label="Поиск сотрудника"
              />
              <select
                className="form-input"
                value={assignDept}
                onChange={(e) => setAssignDept(e.target.value)}
                aria-label="Фильтр по отделу"
              >
                <option value="__all__">Все отделы</option>
                {assignDepts.map((d) => (
                  <option key={d} value={d}>
                    {d}
                  </option>
                ))}
              </select>
              <select
                className="form-input"
                value={assignClinic}
                onChange={(e) => setAssignClinic(e.target.value)}
                aria-label="Кто в модуле"
              >
                <option value="all">Все</option>
                <option value="with">С учёткой в модуле</option>
                <option value="without">Без учётки (создать)</option>
              </select>
              {assignLoadBusy && <span className="text-muted">Загрузка…</span>}
            </div>
            {assignPeople.length > 0 && (
              <div
                className="publication-visibility__actions"
                style={{ marginTop: '0.45rem', marginBottom: '0.25rem' }}
              >
                <button
                  type="button"
                  className="btn btn-ghost btn--sm"
                  onClick={selectAllVisible}
                  disabled={assignLoadBusy}
                >
                  Выбрать всех ({assignPeople.length})
                </button>
              </div>
            )}
            {assignPeople.length > 0 ? (
              <div className="assign-list" role="group" aria-label="Список сотрудников">
                {assignPeople.map((p) => {
                  const k = assignPersonKey(p);
                  const picked = assignSelected.has(k);
                  return (
                    <label
                      key={k}
                      className={`assign-row${picked ? ' assign-row--selected' : ''}`}
                    >
                      <input
                        type="checkbox"
                        checked={picked}
                        onChange={() => toggleAssignPerson(p)}
                      />
                      <span className="assign-row__text">
                        <span className="assign-row__fio">{p.fio}</span>
                        {p.webLogin && <span className="assign-row__login">{p.webLogin}</span>}
                        <span className="assign-row__meta">
                          {p.departments || '—'}
                          {p.clinicUserId
                            ? ' · есть учётка в модуле'
                            : ' · нет учётки (создадим при назначении)'}
                        </span>
                      </span>
                    </label>
                  );
                })}
              </div>
            ) : (
              !assignLoadBusy && (
                <p className="text-muted" style={{ margin: '0.5rem 0' }}>
                  Нет подходящих записей. Смените фильтры или поиск.
                </p>
              )
            )}
            <div className="publication-visibility__actions" style={{ marginTop: '0.75rem' }}>
              <button
                type="button"
                className="btn btn-ghost"
                disabled={assignSelectedInList.length === 0}
                onClick={postAssign}
              >
                Назначить выбранных
                {assignSelectedInList.length > 0
                  ? ` (${assignSelectedInList.length})`
                  : ''}
              </button>
            </div>
            {assignMsg && <p className="text-muted" style={{ marginTop: 8, marginBottom: 0 }}>{assignMsg}</p>}
          </div>
        )}
      </AccSection>

      <div
        className="test-detail-fixed-actions"
        role="region"
        aria-label="Быстрые действия с черновиком"
      >
        <div className="test-detail-fixed-actions__inner">
          {draftStatus && (
            <p className="muted test-detail-fixed-actions__status" role="status">
              {draftStatus}
            </p>
          )}
          <div className="actions-bar">
            <button type="button" className="btn btn-primary" onClick={saveDraft}>
              Сохранить черновик
            </button>
            <Link to="/tests" className="btn btn-ghost">
              К списку
            </Link>
          </div>
        </div>
      </div>
    </div>
  );
}
