/* превью-файлов-в-хранилище.js — эскизы картинок и просмотр видео в «Файловом хранилище» (админка).
   Тема аккаунта, отдельный JS-файл темы. Страница: Профиль → «Файловое хранилище» (/pl/fileindex/file/index).
   Разметка GC (проверено 02.10.2026): table.gc-fileservice-table > tr.fileindex-grid; в ячейке названия —
   p.gc-fileservice-file-name > a.gc-btn-download (ссылка на файл) + span.gc-fileservice-file-name--value;
   у картинок в ячейке типа — span.gc-fileservice-image-icon с эскизом 40×40 в background-image,
   у остальных — span.gc-fileservice-file-type-icon.<расширение>.
   Что делает: под названием картинки — эскиз (на экране 96 px по высоте, картинка у сервиса эскизов GC — 160 px
   для чёткости на ретине);
   под названием видео — кнопка «Смотреть», по нажатию открывается небольшой плеер (до нажатия видео
   не грузится, чтобы не тянуть трафик). Не зависит от порядка колонок — ищет по классам.
   Таблица перерисовывается при фильтрах и листании — скрипт следит за ней и дорисовывает новые строки.
   Оформление встроено (с 22.10.2026): скрипт сам добавляет на страницу хранилища блок стилей <style id="vs-fs-style">.
   Цвета и отступы — переменные темы школы с запасными значениями (те же, что в 00-токены.css), поэтому файл
   работает и в теме без наших токенов, и в полной теме аккаунта. Отдельный CSS-файл не нужен.
   Защита от двойного подключения: если файл случайно стоит в теме дважды, второй экземпляр ничего не делает,
   стили добавляются один раз.
   Безопасность: адреса берутся только с файловых серверов GetCourse (https://fs….getcourse.ru), в DOM — только
   createElement / setAttribute; сети скрипт сам не касается (грузит браузер — картинка и видео). */
(function () {
  'use strict';

  /* Файл уже запущен (стоит в теме дважды) — второй раз не работаем. */
  if (window.vsFsPreview) return;
  window.vsFsPreview = true;

  /* ---------- Настройки (можно править вручную) ---------- */

  /* Высота эскиза картинки, px (сервис эскизов GC отдаёт копию по высоте). */
  var THUMB_HEIGHT = 160;
  /* Расширения видео, которые браузер умеет показать сам. */
  var VIDEO_EXT = ['mp4', 'webm', 'm4v', 'mov'];
  /* Сколько строк обрабатывать за один проход (на странице хранилища обычно 20–100). */
  var MAX_ROWS = 300;

  /* ---------- Оформление (вставляется на страницу один раз) ----------
     Эскиз под названием картинки (a.vs-fs-thumb > img), кнопка «Смотреть» (button.vs-fs-play) и плеер вместо неё
     (video.vs-fs-video). var(--токен, запасное значение): есть тема школы — берутся её токены, нет — запасные. */
  var STYLE_ID = 'vs-fs-style';
  var CSS = [
    /* Рамка эскиза: блок под названием, скругление как у полей. */
    '.gc-fileservice-table .vs-fs-thumb { display: block; width: max-content; max-width: 100%;',
    '  margin-top: var(--space-2xs, 8px); margin-left: 32px; line-height: 0;',
    '  border: var(--border-width, 1px) solid var(--divider, #dedee6); border-radius: var(--radius-control, 8px); overflow: hidden; }',
    /* Ячейка названия у GC — flex без переноса, а название обрезано в одну строку шириной 357 px (line-clamp).
       У картинок и видео (там, где скрипт что-то добавил) название показываем целиком, с переносом строк,
       а эскиз или кнопка «Смотреть» уходят под него (отступ слева 32 px — под значок скачивания). */
    '.gc-fileservice-table .gc-fileservice-file-name:has(.vs-fs-thumb, .vs-fs-play, .vs-fs-video) { flex-wrap: wrap; }',
    '.gc-fileservice-table .gc-fileservice-file-name:has(.vs-fs-thumb, .vs-fs-play, .vs-fs-video) .gc-fileservice-file-name--value {',
    '  flex: 1 1 calc(100% - 44px); min-width: 0; max-width: none; display: block;',
    '  -webkit-line-clamp: none; overflow: visible; white-space: normal; overflow-wrap: anywhere; }',
    '.gc-fileservice-table .vs-fs-play, .gc-fileservice-table .vs-fs-video { margin-left: 32px; }',
    /* Эскиз 96 px по высоте (картинка у сервиса эскизов — 160 px, для чёткости на ретине), не шире 240 px. */
    '.gc-fileservice-table .vs-fs-thumb img { display: block; max-width: 240px; height: 96px; width: auto; object-fit: contain; background: var(--surface-sunken, #e9e9f0); }',
    '.gc-fileservice-table .vs-fs-thumb:hover { border-color: var(--accent, #9c5479); }',
    /* Кнопка «Смотреть» — белая кнопка админки, как .btn-default (Р-113). */
    '.gc-fileservice-table .vs-fs-play { display: inline-flex; align-items: center; min-height: 32px;',
    '  margin-top: var(--space-2xs, 8px); padding: 0 var(--space-s, 16px);',
    '  border: var(--border-width, 1px) solid var(--divider, #dedee6); border-radius: var(--radius-control, 8px);',
    '  background: var(--surface, #ffffff); color: var(--text, #0f0f21);',
    '  font-size: var(--fs-small, 14px); font-weight: var(--fw-button, 600); cursor: pointer; }',
    /* Значок &#9654; кодом: GC портит живые символы в файлах. */
    ".gc-fileservice-table .vs-fs-play::before { content: '\\25B6'; margin-right: 6px; font-size: .8em; }",
    '.gc-fileservice-table .vs-fs-play:hover { background: var(--surface-sunken, #e9e9f0); }',
    /* Плеер видео после нажатия «Смотреть». */
    '.gc-fileservice-table .vs-fs-video { display: block; width: 320px; max-width: 100%; margin-top: var(--space-2xs, 8px);',
    '  border-radius: var(--radius-control, 8px); background: var(--n-8, #0f0f21); }'
  ].join('\n');

  /* Добавить стили на страницу, если их ещё нет. */
  function addStyle() {
    if (document.getElementById(STYLE_ID)) return;
    var style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = CSS;
    (document.head || document.documentElement).appendChild(style);
  }

  /* ---------- Помощники ---------- */

  /* Только файловые серверы GetCourse по https (или без протокола, как пишет GC). */
  var FS_HOST = /^(https:)?\/\/fs(-thb\d*)?\.getcourse\.ru\/fileservice\/file\//;

  function el(tag, cls, text) {
    var node = document.createElement(tag);
    if (cls) node.className = cls;
    if (text) node.textContent = text;
    return node;
  }

  /* Адрес эскиза из background-image: url('…/s/40x40/…') → тот же адрес с размером x<THUMB_HEIGHT>. */
  function thumbUrl(icon) {
    var bg = icon.style && icon.style.backgroundImage ? icon.style.backgroundImage : '';
    var m = /url\(["']?([^"')]+)["']?\)/.exec(bg);
    if (!m || !FS_HOST.test(m[1])) return '';
    var url = m[1].replace(/\/s\/\d*x\d+\//, '/s/x' + THUMB_HEIGHT + '/');
    return url.indexOf('//') === 0 ? 'https:' + url : url;
  }

  /* Расширение видео из класса значка типа: «gc-fileservice-file-type-icon mp4» → 'mp4'. */
  function videoExt(row) {
    var icon = row.querySelector('.gc-fileservice-file-type-icon');
    if (!icon) return '';
    var i;
    for (i = 0; i < VIDEO_EXT.length; i++) {
      if (icon.classList.contains(VIDEO_EXT[i])) return VIDEO_EXT[i];
    }
    return '';
  }

  /* ---------- Строка таблицы ---------- */

  function enhanceRow(row) {
    if (row.hasAttribute('data-vs-preview')) return;
    row.setAttribute('data-vs-preview', '1');

    var nameBox = row.querySelector('.gc-fileservice-file-name');
    var link = nameBox && nameBox.querySelector('a.gc-btn-download[href]');
    if (!nameBox || !link) return;
    var fileUrl = link.getAttribute('href') || '';
    if (!FS_HOST.test(fileUrl)) return;
    var nameNode = nameBox.querySelector('.gc-fileservice-file-name--value');
    var name = nameNode ? (nameNode.textContent || '').trim().slice(0, 200) : '';

    /* Картинка: эскиз-ссылка на сам файл (откроется в новой вкладке). */
    var icon = row.querySelector('.gc-fileservice-image-icon');
    if (icon) {
      var src = thumbUrl(icon);
      if (!src) return;
      var a = el('a', 'vs-fs-thumb');
      a.setAttribute('href', fileUrl);
      a.setAttribute('target', '_blank');
      a.setAttribute('rel', 'noopener');
      var img = el('img');
      img.setAttribute('src', src);
      img.setAttribute('alt', name ? 'Эскиз: ' + name : 'Эскиз картинки');
      img.setAttribute('loading', 'lazy');
      img.setAttribute('decoding', 'async');
      /* Эскиз не получился — убираем пустую рамку. */
      img.addEventListener('error', function () { if (a.parentNode) a.parentNode.removeChild(a); });
      a.appendChild(img);
      nameBox.appendChild(a);
      return;
    }

    /* Видео: кнопка, плеер создаётся только по нажатию. */
    var ext = videoExt(row);
    if (!ext) return;
    var btn = el('button', 'vs-fs-play', 'Смотреть');
    btn.type = 'button';
    btn.setAttribute('aria-label', 'Смотреть видео' + (name ? ': ' + name : ''));
    btn.addEventListener('click', function () {
      var video = el('video', 'vs-fs-video');
      video.setAttribute('controls', '');
      video.setAttribute('preload', 'metadata');
      video.setAttribute('playsinline', '');
      video.setAttribute('src', fileUrl);
      btn.parentNode.replaceChild(video, btn);
      video.focus();
    });
    nameBox.appendChild(btn);
  }

  function run() {
    var rows = document.querySelectorAll('table.gc-fileservice-table tr.fileindex-grid:not([data-vs-preview])');
    var n = Math.min(rows.length, MAX_ROWS);
    var i;
    for (i = 0; i < n; i++) enhanceRow(rows[i]);
  }

  /* Следим за областью страницы: фильтры и листание перерисовывают таблицу (иногда вместе с контейнером)
     без перезагрузки. Проход — не чаще раза в кадр и только по новым строкам. */
  function watch() {
    var host = document.querySelector('.gc-main-content') || document.body;
    if (!host || typeof window.MutationObserver !== 'function') return;
    var pending = false;
    var obs = new MutationObserver(function () {
      if (pending) return;
      pending = true;
      window.requestAnimationFrame(function () { pending = false; run(); });
    });
    obs.observe(host, { childList: true, subtree: true });
  }

  function start() {
    if (!/^\/pl\/fileindex\//.test(window.location.pathname)) return;
    if (!document.querySelector('table.gc-fileservice-table')) return;
    addStyle();
    run();
    watch();
  }

  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
  else start();
})();