/* Звуковая библиотека (/admin/sounds).
   Один глобальный <audio>: при запуске нового звука предыдущий останавливается.
   Волноформа и длительность считаются на клиенте (Web Audio API) и кэшируются на сервере. */
(function () {
  'use strict';

  var app = document.getElementById('sounds-app');
  if (!app) return;

  var CSRF = (document.querySelector('meta[name="csrf-token"]') || { content: '' }).content || '';
  var BASE = '/admin/sounds';
  var RENDER_LIMIT = 400;
  var PEAK_BARS = 64;
  var PEAK_MAX_FILE = 40 * 1024 * 1024;

  var $ = function (sel, root) { return (root || app).querySelector(sel); };
  var $$ = function (sel, root) { return Array.prototype.slice.call((root || app).querySelectorAll(sel)); };

  // ---------- Иконки (inline SVG, currentColor, стиль Lucide) ----------
  function svg(inner, filled) {
    return '<svg width="16" height="16" viewBox="0 0 24 24" fill="' + (filled ? 'currentColor' : 'none') +
      '" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + inner + '</svg>';
  }
  var I = {
    play: svg('<polygon points="7 4 20 12 7 20 7 4" stroke="none"/>', true),
    pause: svg('<rect x="6" y="4" width="4" height="16" rx="1" stroke="none"/><rect x="14" y="4" width="4" height="16" rx="1" stroke="none"/>', true),
    star: svg('<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>'),
    download: svg('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M7 10l5 5 5-5M12 15V3"/>'),
    edit: svg('<path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4z"/>'),
    note: '<svg class="snd-cat-ico" width="30" height="30" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18V5l12-2v13"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="16" r="3"/></svg>',
    check: svg('<path d="M20 6 9 17l-5-5"/>'),
    trash: svg('<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="M10 11v6M14 11v6"/>')
  };

  // ---------- Состояние ----------
  var state = {
    sounds: [],
    categories: [],
    uncategorized: 0,
    tab: 'all',   // all | fav
    cat: -1,      // -1 все, 0 без категории, иначе id
    q: '',
    sort: 'new'
  };

  var audio = new Audio();
  audio.preload = 'auto';
  var playing = { id: 0, sound: null };
  var peaksInFlight = {};
  var cssCache = {};

  // ---------- Утилиты ----------
  function cssVar(name, fallback) {
    var v = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
    return v || fallback;
  }
  function fmtDur(sec) {
    if (!sec || sec <= 0) return '—';
    if (sec < 10) { var d = Math.round(sec * 10) / 10; return (d % 1 === 0 ? Math.round(d) : d) + 's'; }
    if (sec < 60) return Math.round(sec) + 's';
    var m = Math.floor(sec / 60), s = Math.round(sec % 60);
    if (s === 60) { m++; s = 0; }
    return m + ':' + String(s).padStart(2, '0');
  }
  function fmtSize(b) {
    if (b >= 1048576) return (Math.round(b / 1048576 * 10) / 10) + ' МБ';
    if (b >= 1024) return Math.round(b / 1024) + ' КБ';
    return b + ' Б';
  }
  function fmtCount(n) {
    if (n >= 1000) return (Math.round(n / 100) / 10) + 'K';
    return String(n);
  }
  function extOf(url) {
    var m = /\.[a-z0-9]+$/i.exec(url || '');
    return m ? m[0] : '';
  }
  function catName(id) {
    if (!id) return '';
    for (var i = 0; i < state.categories.length; i++) {
      if (state.categories[i].id === id) return state.categories[i].name;
    }
    return '';
  }
  function toast(msg, isError) {
    var box = $('[data-snd-toasts]');
    var t = document.createElement('div');
    t.className = 'snd-toast' + (isError ? ' error' : '');
    t.textContent = msg;
    box.appendChild(t);
    setTimeout(function () { t.remove(); }, isError ? 6000 : 3500);
  }

  function api(path, formData) {
    var opt = { method: 'POST', headers: { 'X-CSRF-Token': CSRF }, credentials: 'same-origin' };
    if (formData) opt.body = formData;
    return fetch(BASE + path, opt).then(function (r) {
      return r.json().catch(function () { return {}; }).then(function (j) {
        if (!r.ok || j.error) throw new Error(j.error || ('HTTP ' + r.status));
        return j;
      });
    });
  }

  // ---------- Загрузка данных ----------
  function load() {
    return fetch(BASE + '/data', { credentials: 'same-origin' })
      .then(function (r) { return r.json(); })
      .then(function (j) {
        state.sounds = j.sounds || [];
        state.categories = j.categories || [];
        state.uncategorized = j.uncategorized || 0;
        renderCats();
        renderList();
        fillCatSelects();
        queueDurationProbe();
      })
      .catch(function (e) {
        $('[data-snd-list]').innerHTML = '';
        toast('Не удалось загрузить библиотеку: ' + e.message, true);
      });
  }

  // ---------- Категории (лента карточек) ----------
  function renderCats() {
    var wrap = $('[data-snd-cats]');
    wrap.innerHTML = '';
    var items = state.categories.slice();
    if (state.uncategorized > 0) items.push({ id: 0, name: 'Без категории', count: state.uncategorized });
    items.forEach(function (c) {
      var btn = document.createElement('button');
      btn.type = 'button';
      btn.className = 'snd-cat-card ' + (c.id === 0 ? 'snd-cat-gu' : 'snd-cat-g' + (c.id % 8)) +
        (state.cat === c.id ? ' active' : '');
      btn.innerHTML = I.note;
      var name = document.createElement('span');
      name.className = 'snd-cat-name';
      name.textContent = c.name;
      var count = document.createElement('span');
      count.className = 'snd-cat-count';
      count.textContent = c.count + ' зв.';
      btn.appendChild(name);
      btn.appendChild(count);
      btn.addEventListener('click', function () {
        state.cat = (state.cat === c.id) ? -1 : c.id;
        renderCats();
        renderList();
      });
      wrap.appendChild(btn);
    });
  }

  // ---------- Список ----------
  function visibleSounds() {
    var arr = state.sounds;
    if (state.tab === 'fav') arr = arr.filter(function (s) { return s.fav; });
    if (state.cat >= 0) arr = arr.filter(function (s) { return s.cat === state.cat; });
    var q = state.q.trim().toLowerCase();
    if (q) {
      arr = arr.filter(function (s) {
        return s.name.toLowerCase().indexOf(q) !== -1 ||
          catName(s.cat).toLowerCase().indexOf(q) !== -1;
      });
    }
    var sorted = arr.slice();
    switch (state.sort) {
      case 'old': sorted.sort(function (a, b) { return a.id - b.id; }); break;
      case 'name': sorted.sort(function (a, b) { return a.name.localeCompare(b.name, 'ru'); }); break;
      case 'dur': sorted.sort(function (a, b) { return (b.duration || 0) - (a.duration || 0); }); break;
      case 'plays': sorted.sort(function (a, b) { return b.plays - a.plays; }); break;
      default: sorted.sort(function (a, b) { return b.id - a.id; });
    }
    return sorted;
  }

  function renderList() {
    var list = $('[data-snd-list]');
    list.innerHTML = '';
    var items = visibleSounds();

    if (!items.length) {
      var empty = document.createElement('div');
      empty.className = 'empty-state';
      var p = document.createElement('p');
      if (!state.sounds.length) {
        p.textContent = 'Библиотека пуста. Загрузите звуки через панель или положите файлы в папку inbox и нажмите «Сканировать папку».';
      } else {
        p.textContent = 'Ничего не найдено. Измените запрос или фильтры.';
      }
      empty.appendChild(p);
      list.appendChild(empty);
      return;
    }

    items.slice(0, RENDER_LIMIT).forEach(function (s) { list.appendChild(buildRow(s)); });

    if (items.length > RENDER_LIMIT) {
      var note = document.createElement('div');
      note.className = 'snd-count-note';
      note.textContent = 'Показаны первые ' + RENDER_LIMIT + ' из ' + items.length + ' — уточните поиск или фильтр.';
      list.appendChild(note);
    }
  }

  function buildRow(s) {
    var row = document.createElement('div');
    row.className = 'snd-row' + (playing.id === s.id ? ' playing' : '');
    row.setAttribute('data-snd-id', s.id);

    var playBtn = document.createElement('button');
    playBtn.type = 'button';
    playBtn.className = 'snd-play';
    playBtn.title = 'Прослушать';
    playBtn.innerHTML = (playing.id === s.id && !audio.paused) ? I.pause : I.play;
    playBtn.addEventListener('click', function () { togglePlay(s); });

    var nameWrap = document.createElement('div');
    nameWrap.className = 'snd-name-wrap';
    var name = document.createElement('div');
    name.className = 'snd-name';
    name.textContent = s.name;
    name.title = s.name + ' · ' + fmtSize(s.size);
    var cat = document.createElement('div');
    cat.className = 'snd-cat-label';
    cat.textContent = s.cat ? catName(s.cat) : 'Без категории';
    nameWrap.appendChild(name);
    nameWrap.appendChild(cat);

    var wave = document.createElement('div');
    wave.className = 'snd-wave';
    wave.title = 'Клик — перемотка';
    var canvas = document.createElement('canvas');
    wave.appendChild(canvas);
    wave.addEventListener('click', function (e) {
      var rect = wave.getBoundingClientRect();
      var ratio = Math.min(1, Math.max(0, (e.clientX - rect.left) / rect.width));
      seekTo(s, ratio);
    });

    var dur = document.createElement('div');
    dur.className = 'snd-dur';
    dur.textContent = fmtDur(s.duration);

    var plays = document.createElement('div');
    plays.className = 'snd-plays';
    plays.textContent = s.plays ? fmtCount(s.plays) : '—';
    plays.title = 'Прослушиваний: ' + s.plays + ' · скачиваний: ' + s.downloads;

    var actions = document.createElement('div');
    actions.className = 'snd-actions';

    var dl = document.createElement('button');
    dl.type = 'button';
    dl.className = 'snd-abtn hide-sm';
    dl.title = 'Скачать';
    dl.innerHTML = I.download;
    dl.addEventListener('click', function () { download(s); });

    var fav = document.createElement('button');
    fav.type = 'button';
    fav.className = 'snd-abtn' + (s.fav ? ' faved' : '');
    fav.title = s.fav ? 'Убрать из избранного' : 'В избранное';
    fav.innerHTML = I.star;
    fav.addEventListener('click', function () { toggleFav(s); });

    var edit = document.createElement('button');
    edit.type = 'button';
    edit.className = 'snd-abtn hide-sm';
    edit.title = 'Изменить';
    edit.innerHTML = I.edit;
    edit.addEventListener('click', function () { openEdit(s); });

    actions.appendChild(dl);
    actions.appendChild(fav);
    actions.appendChild(edit);

    row.appendChild(playBtn);
    row.appendChild(nameWrap);
    row.appendChild(wave);
    row.appendChild(dur);
    row.appendChild(plays);
    row.appendChild(actions);

    drawWave(canvas, s, progressFor(s.id));
    return row;
  }

  function rowOf(id) { return $('[data-snd-id="' + id + '"]'); }
  function waveCanvas(id) {
    var row = rowOf(id);
    return row ? row.querySelector('canvas') : null;
  }
  function progressFor(id) {
    if (playing.id !== id || !audio.duration || !isFinite(audio.duration)) return 0;
    return audio.currentTime / audio.duration;
  }

  // ---------- Волноформа ----------
  function drawWave(canvas, s, progress) {
    var w = canvas.parentNode.clientWidth || 216;
    var h = canvas.parentNode.clientHeight || 30;
    var dpr = window.devicePixelRatio || 1;
    if (canvas.width !== w * dpr || canvas.height !== h * dpr) {
      canvas.width = w * dpr;
      canvas.height = h * dpr;
    }
    var ctx = canvas.getContext('2d');
    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
    ctx.clearRect(0, 0, w, h);

    var accent = cssVar('--accent', '#6d5cff');
    var base = cssVar('--border', '#c9ced6');
    var mid = h / 2;
    var n = PEAK_BARS;
    var step = w / n;
    var barW = Math.max(2, step - 1.4);

    if (!s.peaks) {
      // Метаданных ещё нет — рисуем ровный пунктир (как «непрослушанный» трек).
      ctx.fillStyle = base;
      for (var i = 0; i < n; i++) {
        ctx.fillRect(i * step, mid - 1, barW, 2);
      }
      return;
    }
    var peaks = s.peaks;
    var count = peaks.length || 1;
    for (var j = 0; j < n; j++) {
      var p = peaks[Math.floor(j * count / n)] || 0;
      var bh = Math.max(2, p * (h - 4));
      ctx.fillStyle = (progress > 0 && j / n <= progress) ? accent : base;
      ctx.fillRect(j * step, mid - bh / 2, barW, bh);
    }
  }

  function computePeaks(s) {
    if (s.peaks || peaksInFlight[s.id] || s.size > PEAK_MAX_FILE) return;
    var AC = window.AudioContext || window.webkitAudioContext;
    if (!AC) return;
    peaksInFlight[s.id] = true;
    fetch(s.url, { credentials: 'same-origin' })
      .then(function (r) { return r.arrayBuffer(); })
      .then(function (buf) {
        var ctx = new AC();
        return ctx.decodeAudioData(buf).then(function (ab) {
          var ch = ab.getChannelData(0);
          var block = Math.max(1, Math.floor(ch.length / PEAK_BARS));
          var sample = Math.max(1, Math.floor(block / 64));
          var peaks = [];
          for (var i = 0; i < PEAK_BARS; i++) {
            var m = 0, start = i * block, end = Math.min(ch.length, start + block);
            for (var j = start; j < end; j += sample) {
              var v = Math.abs(ch[j]);
              if (v > m) m = v;
            }
            peaks.push(m);
          }
          var max = Math.max.apply(null, peaks.concat([0.01]));
          s.peaks = peaks.map(function (p) { return Math.round(p / max * 1000) / 1000; });
          if (!s.duration && ab.duration) s.duration = ab.duration;
          ctx.close();

          var fd = new FormData();
          fd.append('peaks', JSON.stringify(s.peaks));
          if (ab.duration) fd.append('duration', String(ab.duration));
          api('/' + s.id + '/meta', fd).catch(function () {});

          var canvas = waveCanvas(s.id);
          if (canvas) drawWave(canvas, s, progressFor(s.id));
          updateCells(s);
        });
      })
      .catch(function () {})
      .then(function () { delete peaksInFlight[s.id]; });
  }

  // ---------- Плеер: играет только один звук ----------
  function togglePlay(s) {
    if (playing.id === s.id) {
      if (audio.paused) audio.play().catch(function () {});
      else audio.pause();
      syncPlayButtons();
      return;
    }
    var prev = playing.id;
    playing = { id: s.id, sound: s };
    audio.src = s.url;
    audio.play().catch(function (e) {
      // AbortError — нормальная ситуация при быстром переключении треков.
      if (e && e.name === 'AbortError') return;
      toast('Не удалось воспроизвести: ' + e.message, true);
    });

    if (prev) {
      var prevRow = rowOf(prev);
      if (prevRow) prevRow.classList.remove('playing');
      var prevSound = null;
      for (var i = 0; i < state.sounds.length; i++) if (state.sounds[i].id === prev) prevSound = state.sounds[i];
      var prevCanvas = waveCanvas(prev);
      if (prevCanvas && prevSound) drawWave(prevCanvas, prevSound, 0);
    }
    var row = rowOf(s.id);
    if (row) row.classList.add('playing');
    syncPlayButtons();

    s.plays++;
    updateCells(s);
    api('/' + s.id + '/played', null).catch(function () {});
    if (!s.peaks) computePeaks(s);
  }

  function seekTo(s, ratio) {
    if (playing.id === s.id && audio.duration && isFinite(audio.duration)) {
      audio.currentTime = ratio * audio.duration;
      if (audio.paused) audio.play().catch(function () {});
      syncPlayButtons();
    } else {
      togglePlay(s);
    }
  }

  function syncPlayButtons() {
    $$('.snd-row').forEach(function (row) {
      var id = parseInt(row.getAttribute('data-snd-id'), 10);
      var btn = row.querySelector('.snd-play');
      if (!btn) return;
      btn.innerHTML = (id === playing.id && !audio.paused) ? I.pause : I.play;
    });
  }

  audio.addEventListener('ended', function () {
    var s = playing.sound;
    var canvas = s ? waveCanvas(s.id) : null;
    var row = s ? rowOf(s.id) : null;
    playing = { id: 0, sound: null };
    if (row) row.classList.remove('playing');
    if (canvas && s) drawWave(canvas, s, 0);
    syncPlayButtons();
  });

  audio.addEventListener('loadedmetadata', function () {
    var s = playing.sound;
    if (!s || !audio.duration || !isFinite(audio.duration)) return;
    if (!s.duration || Math.abs(s.duration - audio.duration) > 1) {
      s.duration = audio.duration;
      var fd = new FormData();
      fd.append('duration', String(audio.duration));
      api('/' + s.id + '/meta', fd).catch(function () {});
      updateCells(s);
    }
  });

  audio.addEventListener('pause', syncPlayButtons);
  audio.addEventListener('play', syncPlayButtons);

  (function progressLoop() {
    if (playing.id && !audio.paused && playing.sound) {
      var canvas = waveCanvas(playing.id);
      if (canvas) drawWave(canvas, playing.sound, progressFor(playing.id));
    }
    requestAnimationFrame(progressLoop);
  })();

  /** Обновить длительность/счётчик в строке без полной перерисовки. */
  function updateCells(s) {
    var row = rowOf(s.id);
    if (!row) return;
    var dur = row.querySelector('.snd-dur');
    if (dur) dur.textContent = fmtDur(s.duration);
    var plays = row.querySelector('.snd-plays');
    if (plays) {
      plays.textContent = s.plays ? fmtCount(s.plays) : '—';
      plays.title = 'Прослушиваний: ' + s.plays + ' · скачиваний: ' + s.downloads;
    }
  }

  // ---------- Фоновое определение длительности (после сканирования папки) ----------
  var probeQueue = [];
  var probing = false;
  function queueDurationProbe() {
    probeQueue = state.sounds.filter(function (s) { return !s.duration; }).slice(0, 24);
    probeNext();
  }
  function probeNext() {
    if (probing) return;
    var s = probeQueue.shift();
    if (!s) return;
    probing = true;
    var a = new Audio();
    a.preload = 'metadata';
    var finished = false;
    var done = function () {
      if (finished) return;
      finished = true;
      probing = false;
      a.removeAttribute('src');
      probeNext();
    };
    var t = setTimeout(done, 8000);
    a.addEventListener('loadedmetadata', function () {
      clearTimeout(t);
      if (a.duration && isFinite(a.duration)) {
        s.duration = a.duration;
        var fd = new FormData();
        fd.append('duration', String(a.duration));
        api('/' + s.id + '/meta', fd).catch(function () {});
        updateCells(s);
      }
      done();
    });
    a.addEventListener('error', function () { clearTimeout(t); done(); });
    a.src = s.url;
  }

  // ---------- Действия ----------
  function toggleFav(s) {
    api('/' + s.id + '/favorite', null)
      .then(function (j) {
        s.fav = !!j.fav;
        renderList();
        syncPlayButtons();
      })
      .catch(function (e) { toast('Ошибка: ' + e.message, true); });
  }

  function download(s) {
    var a = document.createElement('a');
    a.href = s.url;
    a.download = (s.name || 'sound').replace(/[\\/:*?"<>|]+/g, ' ').trim() + extOf(s.url);
    document.body.appendChild(a);
    a.click();
    a.remove();
    s.downloads++;
    api('/' + s.id + '/downloaded', null).catch(function () {});
  }

  // ---------- Модалки ----------
  function openModal(name) {
    var m = $('[data-snd-modal="' + name + '"]');
    if (m) m.hidden = false;
  }
  function closeModal(name) {
    var m = $('[data-snd-modal="' + name + '"]');
    if (m) m.hidden = true;
  }
  $$('[data-snd-open]').forEach(function (btn) {
    btn.addEventListener('click', function () {
      var name = btn.getAttribute('data-snd-open');
      if (name === 'cats') renderCatRows();
      if (name === 'upload') fillCatSelects();
      openModal(name);
    });
  });
  $$('[data-snd-modal]').forEach(function (m) {
    $$('[data-snd-close]', m).forEach(function (btn) {
      btn.addEventListener('click', function () { m.hidden = true; });
    });
  });
  document.addEventListener('keydown', function (e) {
    if (e.key === 'Escape') $$('[data-snd-modal]').forEach(function (m) { m.hidden = true; });
  });

  // ---------- Вкладки / поиск / сортировка ----------
  $$('[data-snd-tab]').forEach(function (btn) {
    btn.addEventListener('click', function () {
      state.tab = btn.getAttribute('data-snd-tab');
      $$('[data-snd-tab]').forEach(function (b) { b.classList.toggle('active', b === btn); });
      renderList();
    });
  });
  var searchTimer = 0;
  $('[data-snd-search]').addEventListener('input', function (e) {
    clearTimeout(searchTimer);
    searchTimer = setTimeout(function () {
      state.q = e.target.value;
      renderList();
    }, 150);
  });
  $('[data-snd-sort]').addEventListener('change', function (e) {
    state.sort = e.target.value;
    renderList();
  });

  // ---------- Редактирование звука ----------
  var editing = null;
  function openEdit(s) {
    editing = s;
    $('[data-snd-edit-name]').value = s.name;
    fillCatSelects();
    $('[data-snd-edit-cat]').value = String(s.cat || 0);
    openModal('edit');
  }
  $('[data-snd-edit-save]').addEventListener('click', function () {
    if (!editing) return;
    var s = editing;
    var fd = new FormData();
    fd.append('name', $('[data-snd-edit-name]').value.trim());
    fd.append('category_id', $('[data-snd-edit-cat]').value);
    api('/' + s.id + '/update', fd)
      .then(function () {
        s.name = $('[data-snd-edit-name]').value.trim() || s.name;
        s.cat = parseInt($('[data-snd-edit-cat]').value, 10) || 0;
        closeModal('edit');
        refreshCounts();
        renderCats();
        renderList();
        syncPlayButtons();
        toast('Сохранено.');
      })
      .catch(function (e) { toast('Ошибка: ' + e.message, true); });
  });
  $('[data-snd-edit-delete]').addEventListener('click', function () {
    if (!editing) return;
    var s = editing;
    if (!confirm('Удалить звук «' + s.name + '»? Файл будет удалён с сервера.')) return;
    api('/' + s.id + '/delete', null)
      .then(function () {
        if (playing.id === s.id) {
          audio.pause();
          audio.removeAttribute('src');
          playing = { id: 0, sound: null };
        }
        state.sounds = state.sounds.filter(function (x) { return x.id !== s.id; });
        closeModal('edit');
        refreshCounts();
        renderCats();
        renderList();
        toast('Звук удалён.');
      })
      .catch(function (e) { toast('Ошибка: ' + e.message, true); });
  });

  /** Пересчитать счётчики категорий по текущему списку звуков. */
  function refreshCounts() {
    var counts = {};
    state.sounds.forEach(function (s) {
      var k = s.cat || 0;
      counts[k] = (counts[k] || 0) + 1;
    });
    state.categories.forEach(function (c) { c.count = counts[c.id] || 0; });
    state.uncategorized = counts[0] || 0;
    if (state.cat > 0 && !catName(state.cat)) state.cat = -1;
  }

  /** Заполнить селекты категорий (upload + edit). */
  function fillCatSelects() {
    [['[data-snd-upload-cat]', true], ['[data-snd-edit-cat]', false]].forEach(function (conf) {
      var sel = $(conf[0]);
      if (!sel) return;
      var current = sel.value;
      sel.innerHTML = '';
      var opt0 = document.createElement('option');
      opt0.value = '0';
      opt0.textContent = 'Без категории';
      sel.appendChild(opt0);
      state.categories.forEach(function (c) {
        var o = document.createElement('option');
        o.value = String(c.id);
        o.textContent = c.name;
        sel.appendChild(o);
      });
      if (conf[1]) {
        var oNew = document.createElement('option');
        oNew.value = 'new';
        oNew.textContent = '+ Новая категория…';
        sel.appendChild(oNew);
      }
      if (current) sel.value = current;
      if (!sel.value) sel.value = '0';
    });
  }
  $('[data-snd-upload-cat]').addEventListener('change', function (e) {
    $('[data-snd-newcat-wrap]').hidden = e.target.value !== 'new';
  });

  // ---------- Управление категориями ----------
  function renderCatRows() {
    var wrap = $('[data-snd-cat-rows]');
    wrap.innerHTML = '';
    if (!state.categories.length) {
      var p = document.createElement('p');
      p.className = 'snd-cat-empty';
      p.textContent = 'Категорий пока нет. Добавьте первую — или создайте подпапку в inbox при сканировании.';
      wrap.appendChild(p);
      return;
    }
    state.categories.forEach(function (c) {
      var row = document.createElement('div');
      row.className = 'snd-cat-row';

      var dot = document.createElement('span');
      dot.className = 'snd-cat-dot snd-cat-g' + (c.id % 8);

      var input = document.createElement('input');
      input.type = 'text';
      input.className = 'input';
      input.value = c.name;
      input.maxLength = 120;

      var cnt = document.createElement('span');
      cnt.className = 'cnt';
      cnt.textContent = c.count + ' зв.';

      var save = document.createElement('button');
      save.type = 'button';
      save.className = 'snd-abtn';
      save.title = 'Сохранить название';
      save.innerHTML = I.check;
      save.addEventListener('click', function () {
        var name = input.value.trim();
        if (!name || name === c.name) return;
        var fd = new FormData();
        fd.append('name', name);
        api('/categories/' + c.id + '/update', fd)
          .then(function () {
            c.name = name;
            renderCats();
            renderList();
            fillCatSelects();
            toast('Категория переименована.');
          })
          .catch(function (e) { toast('Ошибка: ' + e.message, true); });
      });

      var del = document.createElement('button');
      del.type = 'button';
      del.className = 'snd-abtn danger';
      del.title = 'Удалить категорию';
      del.innerHTML = I.trash;
      del.addEventListener('click', function () {
        if (!confirm('Удалить категорию «' + c.name + '»? Звуки останутся в библиотеке без категории.')) return;
        api('/categories/' + c.id + '/delete', null)
          .then(function () {
            state.categories = state.categories.filter(function (x) { return x.id !== c.id; });
            state.sounds.forEach(function (s) { if (s.cat === c.id) s.cat = 0; });
            refreshCounts();
            renderCats();
            renderList();
            fillCatSelects();
            renderCatRows();
            toast('Категория удалена.');
          })
          .catch(function (e) { toast('Ошибка: ' + e.message, true); });
      });

      row.appendChild(dot);
      row.appendChild(input);
      row.appendChild(cnt);
      row.appendChild(save);
      row.appendChild(del);
      wrap.appendChild(row);
    });
  }
  $('[data-snd-cat-add]').addEventListener('click', addCategory);
  $('[data-snd-cat-new]').addEventListener('keydown', function (e) {
    if (e.key === 'Enter') addCategory();
  });
  function addCategory() {
    var input = $('[data-snd-cat-new]');
    var name = input.value.trim();
    if (!name) return;
    var fd = new FormData();
    fd.append('name', name);
    api('/categories/store', fd)
      .then(function () {
        input.value = '';
        return load();
      })
      .then(function () { renderCatRows(); toast('Категория добавлена.'); })
      .catch(function (e) { toast('Ошибка: ' + e.message, true); });
  }

  // ---------- Загрузка файлов ----------
  var uploadFiles = [];
  var drop = $('[data-snd-drop]');
  var fileInput = $('[data-snd-file-input]');
  var goBtn = $('[data-snd-upload-go]');

  drop.addEventListener('click', function () { fileInput.click(); });
  fileInput.addEventListener('change', function () {
    addFiles(fileInput.files);
    fileInput.value = '';
  });
  ['dragover', 'dragenter'].forEach(function (ev) {
    drop.addEventListener(ev, function (e) {
      e.preventDefault();
      drop.classList.add('dragover');
    });
  });
  ['dragleave', 'drop'].forEach(function (ev) {
    drop.addEventListener(ev, function (e) {
      e.preventDefault();
      drop.classList.remove('dragover');
    });
  });
  drop.addEventListener('drop', function (e) {
    if (e.dataTransfer && e.dataTransfer.files) addFiles(e.dataTransfer.files);
  });

  function addFiles(fileList) {
    var allowed = /\.(mp3|wav|ogg|oga|opus|m4a|aac|flac|webm)$/i;
    var rejected = 0;
    Array.prototype.forEach.call(fileList, function (f) {
      if (!allowed.test(f.name)) { rejected++; return; }
      var dup = uploadFiles.some(function (x) { return x.name === f.name && x.size === f.size; });
      if (!dup) uploadFiles.push(f);
    });
    if (rejected) toast('Пропущено файлов с неподдерживаемым форматом: ' + rejected, true);
    renderUploadFiles();
  }

  function renderUploadFiles() {
    var wrap = $('[data-snd-upload-files]');
    wrap.innerHTML = '';
    wrap.hidden = uploadFiles.length === 0;
    goBtn.disabled = uploadFiles.length === 0;
    uploadFiles.forEach(function (f, idx) {
      var row = document.createElement('div');
      row.className = 'snd-upload-file';
      var n = document.createElement('span');
      n.className = 'n';
      n.textContent = f.name;
      var s = document.createElement('span');
      s.className = 's';
      s.textContent = fmtSize(f.size);
      var x = document.createElement('button');
      x.type = 'button';
      x.className = 'x';
      x.title = 'Убрать';
      x.innerHTML = svg('<path d="M18 6 6 18M6 6l12 12"/>');
      x.addEventListener('click', function () {
        uploadFiles.splice(idx, 1);
        renderUploadFiles();
      });
      row.appendChild(n);
      row.appendChild(s);
      row.appendChild(x);
      wrap.appendChild(row);
    });
  }

  function readDuration(file) {
    return new Promise(function (resolve) {
      var url = URL.createObjectURL(file);
      var a = new Audio();
      a.preload = 'metadata';
      var finished = false;
      var done = function (d) {
        if (finished) return;
        finished = true;
        URL.revokeObjectURL(url);
        resolve(d);
      };
      setTimeout(function () { done(0); }, 5000);
      a.addEventListener('loadedmetadata', function () {
        done(a.duration && isFinite(a.duration) ? a.duration : 0);
      });
      a.addEventListener('error', function () { done(0); });
      a.src = url;
    });
  }

  goBtn.addEventListener('click', function () {
    if (!uploadFiles.length) return;
    goBtn.disabled = true;
    goBtn.textContent = 'Подготовка…';

    Promise.all(uploadFiles.map(readDuration)).then(function (durs) {
      var durations = {};
      uploadFiles.forEach(function (f, i) { if (durs[i] > 0) durations[f.name] = durs[i]; });

      var fd = new FormData();
      uploadFiles.forEach(function (f) { fd.append('files[]', f, f.name); });
      fd.append('durations', JSON.stringify(durations));
      var catVal = $('[data-snd-upload-cat]').value;
      if (catVal === 'new') fd.append('new_category', $('[data-snd-newcat]').value.trim());
      else fd.append('category_id', catVal);

      var xhr = new XMLHttpRequest();
      xhr.open('POST', BASE + '/upload');
      xhr.setRequestHeader('X-CSRF-Token', CSRF);
      var prog = $('[data-snd-progress]');
      var bar = $('[data-snd-progress-bar]');
      prog.hidden = false;
      bar.style.width = '0%';
      goBtn.textContent = 'Загрузка…';

      xhr.upload.addEventListener('progress', function (e) {
        if (e.lengthComputable) bar.style.width = Math.round(e.loaded / e.total * 100) + '%';
      });
      xhr.addEventListener('load', function () {
        var j = {};
        try { j = JSON.parse(xhr.responseText); } catch (e) {}
        finishUpload(xhr.status === 200 && j.ok ? j : null, j);
      });
      xhr.addEventListener('error', function () { finishUpload(null, {}); });
      xhr.send(fd);
    });
  });

  function finishUpload(result, raw) {
    var prog = $('[data-snd-progress]');
    prog.hidden = true;
    goBtn.disabled = false;
    goBtn.innerHTML = svg('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12"/>') + ' Загрузить';
    if (!result) {
      toast('Ошибка загрузки' + (raw && raw.error ? ': ' + raw.error : '') + '.', true);
      return;
    }
    uploadFiles = [];
    renderUploadFiles();
    $('[data-snd-newcat]').value = '';
    closeModal('upload');
    toast(summaryText(result));
    load();
  }

  // ---------- Сканирование inbox ----------
  $('[data-snd-scan-go]').addEventListener('click', function () {
    var btn = this;
    var out = $('[data-snd-scan-result]');
    btn.disabled = true;
    out.hidden = false;
    out.textContent = 'Сканирование…';
    api('/scan', null)
      .then(function (j) {
        out.innerHTML = '';
        var main = document.createElement('div');
        main.textContent = summaryText(j);
        out.appendChild(main);
        (j.errors || []).forEach(function (err) {
          var e = document.createElement('div');
          e.className = 'err';
          e.textContent = err;
          out.appendChild(e);
        });
        if (j.imported > 0) load();
      })
      .catch(function (e) {
        out.textContent = 'Ошибка: ' + e.message;
      })
      .then(function () { btn.disabled = false; });
  });

  function summaryText(j) {
    var parts = ['Импортировано: ' + (j.imported || 0)];
    if (j.duplicates) parts.push('дубликатов пропущено: ' + j.duplicates);
    if (j.unsupported) parts.push('неподдерживаемых: ' + j.unsupported);
    if (j.errors && j.errors.length) parts.push('ошибок: ' + j.errors.length);
    return parts.join(', ') + '.';
  }

  // ---------- Старт ----------
  load();
})();
