/*
 * ВИТАМИН D — ВЫГРУЗКА АНКЕТЫ ИЗ КАРТОЧКИ ЗАЯВКИ. Версия 1.2.1.
 * Для таблицы и CSV выгружаются ровно 6 столбцов A–F:
 * профиль, заказ, дата, заполнение, чьи анализы/самочувствие, оригиналы файлов.
 * Поддерживаются новое название поля и старое «Опишите свое самочувствие».
 * Установка в тему: вставьте весь файл в поле JavaScript вашей темы Геткурса
 * без HTML-тегов <script>. Сохраните тему и обновите нужную карточку.
 * Кнопка включается только на online.doc-magomedova.ru по двум шаблонам:
 *   /sales/control/deal/update/id/ЧИСЛОВОЙ_ID
 *   /pl/tasks/task/kanban/deals/ЧИСЛОВОЙ_ID/
 * Параметры после ? и необязательный завершающий / допускаются.
 * Скрипт ждёт загрузки блока «Клиент» и учитывает переходы внутри канбана.
 * Тестовый запуск: откройте нужную заявку в Геткурсе, нажмите F12,
 * выберите Console / Консоль, вставьте ВЕСЬ этот файл и нажмите Enter.
 * В блоке «Клиент» появится кнопка «Выгрузить анкету: витамин D».
 * При запуске из консоли перезагрузка удаляет кнопку; в теме она постоянная.
 * Повторный запуск заменяет предыдущую версию.
 * Удалить без перезагрузки: window.GCVitaminDExporter.destroy()
 *
 * Скрипт читает текущую заявку и страницу анкет через GET в вашей сессии.
 * Заказы, ответы, статусы и настройки он не изменяет. Внешних библиотек нет.
 * Оригиналы открываются отдельными ссылками по нажатию пользователя.
 * CSV / JSON скачиваются на ваш компьютер, строка копируется по нажатию.
 */
(function () {
  'use strict';
  const VERSION = '1.2.1';
  const SURVEY_ID = '435784';
  const KNOWN_SURVEY_IDS = new Set([SURVEY_ID, '435652']);
  const SURVEY_TITLE = 'Анкеты сбора анализов от первых 500 участников АП | Октябрь 2026';
  const PATIENT_FIELD = 'Чьи результаты анализов (пол, возраст) и опишите самочувствие этого человека';
  const FILE_FIELD = 'Загрузите файл с результатами анализов на витамин D3';
  const PREFIX = 'gc-vitd';
  const GLOBAL = 'GCVitaminDExporter';
  if (window[GLOBAL] && typeof window[GLOBAL].destroy === 'function') window[GLOBAL].destroy();
  if (location.hostname !== 'online.doc-magomedova.ru' || !/^https?:$/.test(location.protocol)) return;
  const inPageFamily = /^\/sales\/control\/deal\/update(?:\/|$)/.test(location.pathname)
    || /^\/pl\/tasks\/task\/kanban\/deals(?:\/|$)/.test(location.pathname);
  if (!inPageFamily) return;

  function activeRoute() {
    const order = location.pathname.match(/^\/sales\/control\/deal\/update\/id\/(\d+)\/?$/);
    const kanban = location.pathname.match(/^\/pl\/tasks\/task\/kanban\/deals\/(\d+)\/?$/);
    if (!order && !kanban) return null;
    return {dealId: (order || kanban)[1], kind: order ? 'order' : 'kanban'};
  }

  const $ = (s, root = document) => root.querySelector(s);
  const $$ = (s, root = document) => Array.from(root.querySelectorAll(s));
  const oneLine = value => String(value || '').replace(/\s+/g, ' ').trim();
  const el = (tag, cls, text) => {
    const node = document.createElement(tag);
    if (cls) node.className = cls;
    if (text !== undefined) node.textContent = text;
    return node;
  };
  const textOf = node => {
    if (!node) return '';
    const clone = node.cloneNode(true);
    $$('script,style,noscript,button', clone).forEach(n => n.remove());
    $$('br', clone).forEach(n => n.replaceWith(document.createTextNode('\n')));
    return (clone.textContent || '').replace(/\u00a0/g, ' ').replace(/[ \t]+/g, ' ')
      .split('\n').map(s => s.trim()).filter(Boolean).join('\n');
  };
  function customerPanel() {
    const panels = $$('.panel').filter(p => {
      const h = $(':scope > .panel-heading', p);
      return h && oneLine(textOf(h)).toLowerCase() === 'клиент';
    });
    if (panels.length !== 1) throw new Error('Не найден однозначный блок «Клиент». Откройте полную карточку заявки и повторите запуск.');
    return panels[0];
  }

  function contextOf() {
    const route = activeRoute();
    if (!route) throw new Error('Откройте карточку заказа или заявку канбана.');
    const form = $('#dealForm');
    const action = form && form.getAttribute('action');
    const target = new URL(action || location.href, location.href);
    if (target.origin !== location.origin) throw new Error('Не удалось определить текущую заявку.');
    const formDeal = target.pathname.match(/^\/sales\/control\/deal\/update\/id\/(\d+)\/?$/);
    const hiddenDeal = $('#funnelstagedeal-deal_id');
    const renderedId = formDeal && formDeal[1] || hiddenDeal && hiddenDeal.value || '';
    if (renderedId && renderedId !== route.dealId) throw new Error('Карточка ещё обновляется. Дождитесь загрузки нужной заявки.');
    const panel = customerPanel();
    const links = $$('a[href]', panel).filter(a => /\/user\/control\/user\/update\/id\/\d+(?:$|[/?#])/.test(a.href));
    const ids = Array.from(new Set(links.map(a => a.href.match(/\/update\/id\/(\d+)/)[1])));
    if (ids.length !== 1) throw new Error('Не удалось однозначно определить ID клиента в блоке «Клиент».');
    const userId = ids[0];
    const clone = panel.cloneNode(true);
    $$(`#${PREFIX}-entry`, clone).forEach(n => n.remove());
    const plain = textOf(clone);
    const mailLink = $('a[href^="mailto:"]', clone);
    const emailMatch = plain.match(/[A-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i);
    const email = mailLink ? mailLink.getAttribute('href').slice(7).split('?')[0] : (emailMatch ? emailMatch[0] : '');
    const widget = $(`[data-user-id="${userId}"][data-phone]`, panel);
    const tel = $('a[href^="tel:"]', panel);
    const phoneMatch = textOf($('.editable-link-wrapper', panel)).match(/\+?[\d() .-]{7,}/);
    const phone = oneLine(widget && widget.getAttribute('data-phone') || tel && tel.getAttribute('href').slice(4) || phoneMatch && phoneMatch[0] || '');
    return {
      dealId: route.dealId, dealNumber: oneLine(textOf($('h1'))),
      dealUrl: `${location.origin}/sales/control/deal/update/id/${route.dealId}`,
      userId, name: oneLine(textOf(links[0])), email, phone,
      userUrl: `${location.origin}/user/control/user/update/id/${userId}`,
      surveysUrl: `${location.origin}/user/control/user/surveys/id/${userId}`
    };
  }

  function mount(initial) {
  const app = {
    version: VERSION, controller: null, destroyed: false, selected: null,
    answers: [], context: null, lastError: '', focusBefore: null, busy: false
  };

  function safeURL(raw, base = location.href) {
    try {
      const url = new URL(raw, base);
      const trusted = url.origin === location.origin || /(?:^|\.)getcourse\.ru$/i.test(url.hostname);
      return /^https?:$/.test(url.protocol) && trusted && !url.username && !url.password ? url : null;
    } catch (_) { return null; }
  }

  function fileFrom(raw, label, index) {
    const url = safeURL(raw);
    if (!url || !/(?:\/fileservice\/file\/|\/pl\/fileindex\/file\/view)/.test(url.pathname)) return null;
    const part = name => {
      const m = url.pathname.match(new RegExp('/' + name + '/([^/]+)'));
      return m ? m[1] : '';
    };
    const hash = part('h') || url.searchParams.get('hash') || '';
    const account = part('a');
    const shard = part('sc');
    let original = '';
    let source = 'file-card';
    if (/\/fileservice\/file\/download(?:\/|$)/.test(url.pathname)) {
      original = url.href;
      source = 'direct';
    } else if (/\/fileservice\/file\/thumbnail(?:\/|$)/.test(url.pathname) && hash && account && shard) {
      original = `https://fs.getcourse.ru/fileservice/file/download/a/${account}/sc/${shard}/h/${hash}`;
      source = 'thumbnail-derived';
    }
    let extension = '';
    try { extension = (decodeURIComponent(hash).match(/\.[a-z0-9]{1,8}$/i) || [''])[0]; } catch (_) {}
    const name = oneLine(label);
    return {
      name: name && !/^(скачать|открыть|просмотр|download)$/i.test(name) ? name : `Вложение ${index}${extension}`,
      hash, account, shard, downloadUrl: original,
      previewUrl: /\/thumbnail\//.test(url.pathname) ? url.href : '',
      fileCardUrl: hash ? `${location.origin}/pl/fileindex/file/view?hash=${encodeURIComponent(hash)}` : url.href,
      source
    };
  }

  function parseAnswers(doc) {
    const normalize = text => oneLine(text).toLowerCase().replace(/ё/g, 'е').replace(/[\s*:：]+$/g, '');
    const blocks = $$('.answer-block', doc).filter(block => {
      const surveyClass = Array.from(block.classList).find(c => /^survey-[1-9]\d*$/.test(c));
      if (surveyClass && KNOWN_SURVEY_IDS.has(surveyClass.slice(7))) return true;
      const heading = $('th a[href*="survey-answer/update"]', block);
      const labels = $$('td.key', block).map(k => normalize(textOf(k)));
      return heading && normalize(textOf(heading)) === normalize(SURVEY_TITLE)
        && labels.includes(normalize(PATIENT_FIELD)) && labels.includes(normalize(FILE_FIELD));
    });
    return blocks.map(block => {
      const heading = $('th a[href*="survey-answer/update"]', block);
      // ГК оставляет пустые контейнеры для других вкладок анкет.
      if (!heading && !$$('table.key-value-table td.value', block).length) return null;
      const answerURL = heading && safeURL(heading.getAttribute('href'));
      const id = answerURL && answerURL.searchParams.get('id');
      if (!id || !/^\d+$/.test(id)) throw new Error('У заполнения анкеты не найден ID ответа. Нужна проверка разметки страницы.');
      const surveyClass = Array.from(block.classList).find(c => /^survey-[1-9]\d*$/.test(c));
      const surveyId = surveyClass ? surveyClass.slice(7) : '';
      const answer = {id, surveyId, title: oneLine(textOf(heading)), url: answerURL.href, created: '', status: '', note: '', fields: [], files: []};
      const seen = new Set();
      for (const row of $$('table.key-value-table tr', block)) {
        const key = $(':scope > td.key', row);
        const value = $(':scope > td.value', row);
        if (!key || !value) continue;
        const label = oneLine(textOf(key));
        if (!label) continue;
        const text = textOf(value);
        if (label === 'Создан') { answer.created = text; continue; }
        if (label === 'Статус проверки ответа') { answer.status = text; continue; }
        if (label === 'Примечание') { answer.note = text; continue; }
        const field = {label, text, files: []};
        const anchors = $$('a[href]', value);
        const candidates = anchors.map(a => ({url: a.getAttribute('href'), label: a.getAttribute('download') || textOf(a)}));
        if (!anchors.length) $$('img[src]', value).forEach(img => candidates.push({url: img.getAttribute('src'), label: img.getAttribute('alt')}));
        for (const candidate of candidates) {
          const file = fileFrom(candidate.url, candidate.label, answer.files.length + 1);
          if (!file) continue;
          const identity = file.hash ? `${file.account}/${file.shard}/${file.hash}` : file.downloadUrl || file.fileCardUrl;
          file.question = label;
          if (!seen.has(identity)) { seen.add(identity); answer.files.push(file); }
          if (!field.files.some(f => (f.downloadUrl || f.fileCardUrl) === (file.downloadUrl || file.fileCardUrl))) field.files.push(file);
        }
        answer.fields.push(field);
      }
      if (!answer.fields.length) throw new Error(`В ответе №${id} не найдены поля анкеты.`);
      return answer;
    }).filter(Boolean);
  }

  const style = el('style');
  style.id = `${PREFIX}-style`;
  style.textContent = `
    #${PREFIX}-entry{margin-top:14px;padding-top:12px;border-top:1px solid #e5e7eb}
    #${PREFIX}-entry button{width:100%;white-space:normal}
    #${PREFIX}-modal{position:fixed;inset:0;z-index:2147483000;background:#10251ec0;display:flex;align-items:center;justify-content:center;padding:20px;font:14px/1.5 Arial,sans-serif;color:#172d27}
    #${PREFIX}-modal[hidden]{display:none!important}
    #${PREFIX}-modal *{box-sizing:border-box}
    #${PREFIX}-modal .${PREFIX}-dialog{background:#fff;border-radius:16px;width:920px;max-width:100%;max-height:92vh;display:flex;flex-direction:column;box-shadow:0 20px 90px #0005;overflow:hidden}
    #${PREFIX}-modal .${PREFIX}-top{padding:20px 24px;border-bottom:1px solid #e5ece8;display:flex;align-items:center;gap:16px}
    #${PREFIX}-modal h2{margin:0;font:700 21px/1.3 Arial,sans-serif;flex:1}
    #${PREFIX}-modal .${PREFIX}-content{padding:20px 24px;overflow:auto}
    #${PREFIX}-modal .${PREFIX}-footer{padding:16px 24px;border-top:1px solid #e5ece8;display:flex;gap:8px;flex-wrap:wrap}
    #${PREFIX}-modal button,#${PREFIX}-entry button{font:600 13px/1.35 Arial,sans-serif;cursor:pointer;border:1px solid #b8d5c7;border-radius:8px;padding:10px 13px;background:#f4faf6;color:#153d2c}
    #${PREFIX}-modal button:disabled{opacity:.45;cursor:default}
    #${PREFIX}-modal .${PREFIX}-primary,#${PREFIX}-entry button{background:#087f5b;color:white;border-color:#087f5b}
    #${PREFIX}-modal .${PREFIX}-status{padding:11px 14px;border-radius:8px;background:#edf7f1;white-space:pre-wrap;margin-bottom:16px}
    #${PREFIX}-modal .${PREFIX}-error{background:#fff0ed;color:#9a2d20}
    #${PREFIX}-modal .${PREFIX}-muted{color:#61736a;font-size:12px}
    #${PREFIX}-modal .${PREFIX}-profile{padding:14px;background:#f6f8f7;border-radius:10px;margin-bottom:18px}
    #${PREFIX}-modal .${PREFIX}-field{padding:12px 0;border-bottom:1px solid #edf0ee}
    #${PREFIX}-modal .${PREFIX}-label{font-weight:700;margin-bottom:4px}
    #${PREFIX}-modal .${PREFIX}-value{white-space:pre-wrap;overflow-wrap:anywhere}
    #${PREFIX}-modal .${PREFIX}-links{display:flex;gap:12px;flex-wrap:wrap;margin-top:4px}
    #${PREFIX}-modal a{color:#087b68;text-decoration:underline}
    #${PREFIX}-modal select{width:100%;border:1px solid #bdd0c5;padding:10px;background:white;border-radius:8px;margin:6px 0 14px;font:14px Arial,sans-serif}
    #${PREFIX}-modal textarea{width:100%;height:110px;margin-top:8px;padding:10px;border:1px solid #bdd0c5;border-radius:6px;font:12px/1.5 monospace}
    @media(max-width:650px){#${PREFIX}-modal{padding:8px}#${PREFIX}-modal .${PREFIX}-content,#${PREFIX}-modal .${PREFIX}-top,#${PREFIX}-modal .${PREFIX}-footer{padding:14px}}
  `;
  document.head.appendChild(style);
  const entry = el('div'); entry.id = `${PREFIX}-entry`;
  const launch = el('button', '', 'Выгрузить анкету: витамин D'); launch.type = 'button';
  entry.append(launch);
  ($(':scope > .panel-body', customerPanel()) || customerPanel()).append(entry);

  const modal = el('div'); modal.id = `${PREFIX}-modal`; modal.hidden = true;
  const dialog = el('section', `${PREFIX}-dialog`);
  dialog.setAttribute('role', 'dialog'); dialog.setAttribute('aria-modal', 'true'); dialog.setAttribute('aria-labelledby', `${PREFIX}-title`);
  const top = el('div', `${PREFIX}-top`);
  const title = el('h2', '', 'Анкета: витамин D'); title.id = `${PREFIX}-title`;
  const close = el('button', '', 'Закрыть'); close.type = 'button';
  top.append(title, close);
  const content = el('div', `${PREFIX}-content`);
  const status = el('div', `${PREFIX}-status`); status.setAttribute('role', 'status');
  const result = el('div'); result.id = `${PREFIX}-result`;
  content.append(status, result);
  const footer = el('div', `${PREFIX}-footer`);
  dialog.append(top, content, footer); modal.append(dialog); document.body.append(modal);

  function statusText(text, error = false) {
    status.textContent = text;
    status.className = `${PREFIX}-status${error ? ` ${PREFIX}-error` : ''}`;
  }
  function link(text, href) {
    const url = safeURL(href);
    if (!url) return document.createTextNode('');
    const a = el('a', '', text); a.href = url.href; a.target = '_blank'; a.rel = 'noopener noreferrer';
    return a;
  }
  function button(text, handler, primary = false) {
    const b = el('button', primary ? `${PREFIX}-primary` : '', text); b.type = 'button';
    b.addEventListener('click', async () => {
      b.disabled = true;
      try { await handler(); }
      catch (error) { statusText(error.message || 'Не удалось выполнить действие.', true); }
      finally { b.disabled = false; }
    });
    return b;
  }
  function hide() { modal.hidden = true; if (app.focusBefore && app.focusBefore.isConnected) app.focusBefore.focus(); }
  close.addEventListener('click', hide);
  modal.addEventListener('click', event => { if (event.target === modal) hide(); });
  function onKey(event) {
    if (modal.hidden) return;
    if (event.key === 'Escape') { event.preventDefault(); hide(); }
    if (event.key === 'Tab') {
      const nodes = $$('button:not([disabled]),select,a[href],textarea', dialog);
      const first = nodes[0], last = nodes[nodes.length - 1];
      if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last.focus(); }
      else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); }
    }
  }
  document.addEventListener('keydown', onKey);

  function exportPayload() {
    if (!app.selected || !app.context) throw new Error('Сначала выберите заполнение анкеты.');
    return {version: VERSION, exportedAt: new Date().toISOString(), surveyId: app.selected.surveyId, client: app.context, answer: app.selected};
  }
  function exportColumns() {
    const {client: c, answer: a} = exportPayload();
    const normalize = text => oneLine(text).toLowerCase().replace(/ё/g, 'е').replace(/[\s*:：]+$/g, '');
    let fields = a.fields.filter(f => normalize(f.label) === normalize(PATIENT_FIELD));
    if (!fields.length) fields = a.fields.filter(f => normalize(f.label) === 'опишите свое самочувствие');
    if (!fields.length) throw new Error('В выбранном заполнении не найдено поле о том, чьи это анализы и каково самочувствие. Откройте заполнение в ГК и проверьте название вопроса.');
    if (fields.length !== 1) throw new Error('В заполнении несколько одинаковых полей о самочувствии. Выгрузка остановлена, чтобы не перепутать ответы.');
    return [
      ['Профиль клиента', c.userUrl],
      ['Ссылка на заказ', c.dealUrl],
      ['Дата заполнения (как в ГК)', a.created],
      ['Ссылка на заполнение', a.url],
      [`Анкета — ${PATIENT_FIELD}`, fields[0].text],
      ['Оригиналы файлов', Array.from(new Set(a.files.map(f => f.downloadUrl).filter(Boolean))).join('\n')]
    ];
  }
  const safeCell = value => /^[\s\u0000-\u001f]*[=+@-]/.test(String(value)) ? "'" + String(value) : String(value);
  function tsv(header) { return exportColumns().map(pair => safeCell(pair[header ? 0 : 1]).replace(/[\t\r\n]+/g, ' | ')).join('\t'); }
  function csv() {
    const columns = exportColumns();
    const quote = v => '"' + safeCell(v).replace(/"/g, '""') + '"';
    return '\ufeff' + [columns.map(p => quote(p[0])).join(';'), columns.map(p => quote(p[1])).join(';')].join('\r\n') + '\r\n';
  }
  function download(name, text, type) {
    const url = URL.createObjectURL(new Blob([text], {type}));
    const a = el('a'); a.href = url; a.download = name; document.body.append(a); a.click(); a.remove();
    setTimeout(() => URL.revokeObjectURL(url), 10000);
  }
  async function copy(text, message) {
    try { await navigator.clipboard.writeText(text); statusText(message); }
    catch (_) {
      let area = $(`#${PREFIX}-copy`, dialog);
      if (!area) { area = el('textarea'); area.id = `${PREFIX}-copy`; area.readOnly = true; result.append(area); }
      area.value = text; area.focus(); area.select();
      statusText('Браузер не разрешил автоматическое копирование. Текст выделен ниже: нажмите Ctrl+C или Cmd+C.');
    }
  }
  function renderActions() {
    footer.replaceChildren();
    if (!app.selected) return;
    const stem = `vitamin-d-client-${app.context.userId}-answer-${app.selected.id}`;
    footer.append(
      button('Скопировать строку', () => copy(tsv(false), 'Строка скопирована. Вставьте её в Google Таблицу.'), true),
      button('Скопировать заголовки', () => copy(tsv(true), 'Заголовки скопированы. Вставьте их в первую строку таблицы.')),
      button('Скачать CSV', () => { download(`${stem}.csv`, csv(), 'text/csv;charset=utf-8'); statusText('CSV подготовлен для скачивания.'); }),
      button('Скачать JSON', () => { download(`${stem}.json`, JSON.stringify(exportPayload(), null, 2), 'application/json;charset=utf-8'); statusText('JSON с полными данными подготовлен для скачивания.'); })
    );
  }

  function renderSelection(parent, answer) {
    parent.replaceChildren(); app.selected = answer; renderActions();
    if (!answer) { statusText('У клиента несколько заполнений. Выберите нужное по дате и ID.'); return; }
    const details = el('div', `${PREFIX}-muted`, `Заполнение №${answer.id} · ${answer.created || 'дата не указана'} · ${answer.status || 'статус не указан'}`);
    const source = el('div', `${PREFIX}-links`); source.append(link('Открыть заполнение в ГК', answer.url));
    parent.append(details, source);
    for (const field of answer.fields) {
      const row = el('div', `${PREFIX}-field`);
      row.append(el('div', `${PREFIX}-label`, field.label));
      row.append(el('div', `${PREFIX}-value`, field.text || (field.files.length ? '' : 'Не заполнено')));
      field.files.forEach(file => {
        row.append(el('div', `${PREFIX}-value`, file.name));
        const links = el('div', `${PREFIX}-links`);
        if (file.downloadUrl) links.append(link('Открыть оригинал', file.downloadUrl));
        if (file.previewUrl) links.append(link('Превью', file.previewUrl));
        if (file.fileCardUrl) links.append(link('Карточка файла', file.fileCardUrl));
        row.append(links);
      });
      parent.append(row);
    }
    const missing = answer.files.filter(f => !f.downloadUrl).length;
    statusText(`Собрано полей: ${answer.fields.length}. Вложений: ${answer.files.length}.` +
      (missing ? ` Для ${missing} вложений нет прямой ссылки — используйте карточку файла.` : ''), missing > 0);
  }

  function renderAnswers() {
    result.replaceChildren();
    const c = app.context;
    const profile = el('div', `${PREFIX}-profile`);
    profile.append(el('div', `${PREFIX}-label`, c.name || `Клиент ${c.userId}`));
    profile.append(el('div', `${PREFIX}-value`, `Почта профиля: ${c.email || 'не найдена'}\nТелефон профиля: ${c.phone || 'не найден'}\n${c.dealNumber} · ID клиента ${c.userId}`));
    profile.append(el('div', `${PREFIX}-muted`, 'Введённые клиентом контакты показаны отдельно в ответах анкеты.'));
    const links = el('div', `${PREFIX}-links`); links.append(link('Профиль', c.userUrl), link('Все анкеты клиента', c.surveysUrl)); profile.append(links);
    result.append(profile);
    const body = el('div'); body.id = `${PREFIX}-answer`;
    if (app.answers.length > 1) {
      const label = el('label', `${PREFIX}-label`, 'Какое заполнение выгрузить?'); label.htmlFor = `${PREFIX}-select`;
      const select = el('select'); select.id = `${PREFIX}-select`;
      const empty = el('option', '', 'Выберите заполнение анкеты'); empty.value = ''; select.append(empty);
      app.answers.forEach((answer, i) => { const option = el('option', '', `№${answer.id} · ${answer.created || `заполнение ${i + 1}`}`); option.value = String(i); select.append(option); });
      select.addEventListener('change', () => renderSelection(body, select.value === '' ? null : app.answers[Number(select.value)]));
      result.append(label, select, body); renderSelection(body, null);
    } else { result.append(body); renderSelection(body, app.answers[0]); }
  }

  async function load() {
    if (app.busy) return;
    app.busy = true; launch.disabled = true; app.selected = null; app.answers = []; app.lastError = '';
    result.replaceChildren(); footer.replaceChildren(); statusText('Получаю анкету клиента…');
    const controller = new AbortController(); app.controller = controller;
    let timedOut = false;
    const timer = setTimeout(() => { timedOut = true; controller.abort(); }, 45000);
    try {
      app.context = contextOf();
      const response = await fetch(app.context.surveysUrl, {
        method: 'GET', credentials: 'same-origin', cache: 'no-store', redirect: 'follow',
        headers: {'Accept': 'text/html'}, signal: controller.signal
      });
      if (!response.ok) throw new Error(`Геткурс вернул HTTP ${response.status}. Проверьте доступ к анкетам клиента.`);
      const finalURL = new URL(response.url || app.context.surveysUrl);
      if (finalURL.origin !== location.origin || /\/(?:login|sign-in|signin)(?:\/|$)/i.test(finalURL.pathname)) throw new Error('Открылась страница входа. Войдите в Геткурс и повторите запуск.');
      const html = await response.text();
      const doc = new DOMParser().parseFromString(html, 'text/html');
      if ($('input[type="password"]', doc) && !$('.survey-answers-list', doc)) throw new Error('Вместо анкет получена страница входа или доступа. Проверьте права сотрудника.');
      const current = contextOf();
      if (current.dealId !== app.context.dealId || current.userId !== app.context.userId) throw new Error('Открылась другая заявка. Нажмите кнопку ещё раз в нужной карточке.');
      const profileIds = $$('a[href*="/user/control/user/surveys/id/"]', doc).map(a => (a.getAttribute('href').match(/\/surveys\/id\/(\d+)/) || [])[1]).filter(Boolean);
      if (profileIds.length && !profileIds.includes(app.context.userId)) throw new Error('Получена страница анкет другого пользователя. Выгрузка остановлена.');
      app.answers = parseAnswers(doc);
      if (!app.answers.length) {
        if (!$('#survey-answers-block,.survey-answers-list', doc)) throw new Error('На полученной странице нет раздела анкет. Проверьте права сотрудника или сохраните диагностику.');
        throw new Error('У клиента не найдена заполненная анкета витамина D. Откройте «Все анкеты клиента» и проверьте заполнение.');
      }
      if (app.destroyed) return;
      renderAnswers();
    } catch (error) {
      if (app.destroyed) return;
      app.selected = null; app.answers = [];
      const message = timedOut ? 'Геткурс не ответил за 45 секунд. Повторите попытку.' : (error.message || 'Не удалось загрузить анкету.');
      app.lastError = message; statusText(message, true);
      result.replaceChildren();
      if (app.context) result.append(link('Все анкеты клиента', app.context.surveysUrl));
      footer.replaceChildren(button('Повторить', load), button('Скачать диагностику', () => {
        const diagnostic = {version: VERSION, surveyId: SURVEY_ID, page: location.origin + location.pathname,
          userId: app.context && app.context.userId, dealId: app.context && app.context.dealId, error: app.lastError, at: new Date().toISOString()};
        download('vitamin-d-diagnostic.json', JSON.stringify(diagnostic, null, 2), 'application/json;charset=utf-8');
      }));
      console.error('[Витамин D]', message);
    } finally { clearTimeout(timer); if (!app.destroyed) { app.busy = false; launch.disabled = false; } }
  }

  launch.addEventListener('click', () => {
    app.focusBefore = document.activeElement; modal.hidden = false; close.focus(); load();
  });
  console.info(`[Витамин D ${VERSION}] Кнопка добавлена в блок «Клиент». ID клиента: ${initial.userId}. Нажмите её для загрузки анкеты.`);
  return {
    version: VERSION,
    userId: initial.userId,
    dealId: initial.dealId,
    entry,
    destroy() {
      app.destroyed = true;
      if (app.controller) app.controller.abort();
      document.removeEventListener('keydown', onKey);
      entry.remove(); modal.remove(); style.remove();
    }
  };
  }

  let active = null;
  let observer = null;
  let routeTimer = null;
  let queued = null;
  let stopped = false;
  let waitingReason = '';
  function unmount() {
    if (active) active.destroy();
    active = null;
  }
  function refresh() {
    queued = null;
    if (stopped) return;
    if (!activeRoute()) { unmount(); return; }
    let context;
    try { context = contextOf(); }
    catch (error) { waitingReason = error.message; unmount(); return; }
    waitingReason = '';
    if (active && active.entry.isConnected && active.userId === context.userId && active.dealId === context.dealId) return;
    unmount();
    active = mount(context);
  }
  function schedule() {
    if (!stopped && queued === null) queued = setTimeout(refresh, 120);
  }
  function start() {
    if (stopped) return;
    observer = new MutationObserver(schedule);
    observer.observe(document.body, {childList: true, subtree: true});
    routeTimer = setInterval(() => {
      if (activeRoute() || active) schedule();
    }, 750);
    refresh();
  }
  const manager = {
    version: VERSION,
    refresh: schedule,
    getDiagnostic() { return {version: VERSION, page: location.origin + location.pathname, mounted: !!active, waitingReason}; },
    destroy() {
      stopped = true;
      if (observer) observer.disconnect();
      if (routeTimer !== null) clearInterval(routeTimer);
      if (queued !== null) clearTimeout(queued);
      document.removeEventListener('DOMContentLoaded', start);
      window.removeEventListener('popstate', schedule);
      unmount();
      if (window[GLOBAL] === manager) delete window[GLOBAL];
    }
  };
  window[GLOBAL] = manager;
  window.addEventListener('popstate', schedule);
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start, {once: true});
  else start();
})();