'use strict';
/* ──────────────────────────────────────────────────────────────────
   textbook-tracker.js — injected into each textbook page.
   - "Back to LearnSpace" button overlay (top-left)
   - localStorage progress tracking (always works, even logged out)
   - Server-side sync when authenticated (via LS.api)
   - Per-paragraph "Прочитано" checkbox UI
   ────────────────────────────────────────────────────────────────── */
(function () {
  const slug = (function () {
    const m = location.pathname.match(/\/textbook\/([\w-]+)/);
    if (m) return m[1];
    // Fallback for direct file access during dev
    const fname = location.pathname.split('/').pop().replace(/\.html$/, '');
    // Normalise physics8_* → physics-8-* (e.g. physics8_thermal → physics-8-thermal)
    const norm = fname.replace(/^physics8_/, 'physics-8-');
    return norm.replace(/_/g, '-');
  })();

  const lsKey = 'textbook_progress_' + slug;
  const localState = (function () {
    try { return JSON.parse(localStorage.getItem(lsKey) || '{"read":[],"last":null}'); }
    catch { return { read: [], last: null }; }
  })();
  if (!Array.isArray(localState.read)) localState.read = [];

  /* ── 1. Server sync (best-effort, с очередью) ─────────────────────
     Если POST уже в полёте — следующий патч копится в pendingExtra
     и отправляется после завершения. Так ни один mark_read не
     теряется при быстрых кликах.                                     */
  let syncPending = false;
  let pendingExtra = null;
  function syncToServer(extra) {
    if (typeof LS === 'undefined' || !LS.getToken || !LS.getToken()) return;
    if (syncPending) {
      pendingExtra = Object.assign(pendingExtra || {}, extra || {});
      return;
    }
    syncPending = true;
    fetch('/api/textbooks/' + slug + '/progress', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + LS.getToken(),
      },
      body: JSON.stringify({ last_para: localState.last, ...(extra || {}) }),
    }).finally(() => {
      syncPending = false;
      if (pendingExtra) {
        const next = pendingExtra; pendingExtra = null;
        syncToServer(next);
      }
    }).catch(() => {});
  }

  /* ── 2. Initial load: merge server data into local state + push back ──
     Если в локальном кэше есть ключи, которых нет на сервере
     (последствие старого бага syncPending) — досылаем их через
     отдельные mark_read POST'ы. Это лечит «вечный 0/N» у пользователей,
     которые до фикса уже накликали кучу пилюль.                        */
  function loadServerProgress() {
    if (typeof LS === 'undefined' || !LS.getToken || !LS.getToken()) return;
    fetch('/api/textbooks/' + slug, {
      headers: { 'Authorization': 'Bearer ' + LS.getToken() },
    })
      .then(r => r.ok ? r.json() : null)
      .then(d => {
        if (!d || !d.progress) return;
        const serverRead = new Set(d.progress.read || []);
        const localRead  = localState.read || [];
        const missing    = localRead.filter(k => !serverRead.has(k));
        // объединяем для UI
        const merged = Array.from(new Set([...localRead, ...d.progress.read || []]));
        localState.read = merged;
        if (!localState.last) localState.last = d.progress.last_para;
        localStorage.setItem(lsKey, JSON.stringify(localState));
        refreshAllUI();
        // догоняем сервер последовательно: syncToServer уже коалесцирует
        missing.forEach(k => syncToServer({ mark_read: k }));
      })
      .catch(() => {});
  }

  /* ── 3. Save helpers ──────────────────────────────────────────── */
  function persist() { try { localStorage.setItem(lsKey, JSON.stringify(localState)); } catch {} }

  function setLastPara(key) {
    if (!key) return;
    localState.last = key;
    persist();
    syncToServer({});
  }

  function markRead(key) {
    if (!key || localState.read.includes(key)) return;
    localState.read.push(key);
    persist();
    refreshPillUI(key);
    refreshCheckUI(key);
    syncToServer({ mark_read: key });
  }

  function unmarkRead(key) {
    if (!key) return;
    const i = localState.read.indexOf(key);
    if (i < 0) return;
    localState.read.splice(i, 1);
    persist();
    refreshPillUI(key);
    refreshCheckUI(key);
    syncToServer({ mark_unread: key });
  }

  function toggleRead(key) {
    if (localState.read.includes(key)) unmarkRead(key);
    else markRead(key);
  }

  /* ── 4. UI: back button overlay ───────────────────────────────── */
  function installBackButton() {
    if (document.getElementById('tb-back-btn')) return;
    const btn = document.createElement('a');
    btn.id   = 'tb-back-btn';
    btn.href = '/textbooks';
    btn.title = 'К каталогу учебников';
    btn.innerHTML = `
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><line x1="19" y1="12" x2="5" y2="12"/><polyline points="12 19 5 12 12 5"/></svg>
      <span>Учебники</span>`;
    Object.assign(btn.style, {
      position: 'fixed', top: '10px', left: '12px', zIndex: '9999',
      display: 'inline-flex', alignItems: 'center', gap: '6px',
      padding: '6px 11px 6px 9px', borderRadius: '20px',
      background: 'rgba(0,0,0,.45)', color: '#fff',
      fontFamily: "'Inter',system-ui,sans-serif", fontSize: '12.5px', fontWeight: '700',
      textDecoration: 'none', backdropFilter: 'blur(6px)',
      transition: 'background .15s, transform .12s',
      boxShadow: '0 2px 8px rgba(0,0,0,.18)',
    });
    btn.querySelector('svg').style.cssText = 'width:14px;height:14px;flex-shrink:0';
    btn.addEventListener('mouseenter', () => { btn.style.background = 'rgba(0,0,0,.7)'; btn.style.transform = 'translateY(-1px)'; });
    btn.addEventListener('mouseleave', () => { btn.style.background = 'rgba(0,0,0,.45)'; btn.style.transform = 'none'; });
    document.body.appendChild(btn);
  }

  /* ── 5. UI: mark-read checkboxes near every paragraph heading ─ */
  function installReadCheckboxes() {
    // Each para has a wrapper with id='p1','p2' etc. (data-para usage)
    document.querySelectorAll('[data-para]').forEach(el => {
      // Skip pill buttons (they only navigate); only target paragraph content blocks
      if (el.classList.contains('para-pill')) return;
      injectCheckIntoSection(el);
    });
    // Also look for sections by id matching pN pattern in case data-para isn't on the section
    document.querySelectorAll('section[id^="p"], div[id^="p"]').forEach(el => {
      if (/^p\d+$/.test(el.id) && !el.querySelector(':scope > .tb-readchk')) {
        injectCheckIntoSection(el);
      }
    });
  }

  function injectCheckIntoSection(sectionEl) {
    const key = sectionEl.dataset.para || sectionEl.id;
    if (!key || !/^p\d+/.test(key)) return;
    if (sectionEl.querySelector(':scope > .tb-readchk')) return;

    // Find the first heading inside the section to insert next to it
    const heading = sectionEl.querySelector('h1, h2, h3');
    if (!heading) return;

    const wrap = document.createElement('button');
    wrap.className = 'tb-readchk';
    wrap.dataset.para = key;
    wrap.type = 'button';
    wrap.title = 'Отметить как прочитанное';
    wrap.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg> <span>Прочитано</span>`;
    Object.assign(wrap.style, {
      marginLeft: '12px',
      display: 'inline-flex', alignItems: 'center', gap: '5px',
      padding: '4px 10px', borderRadius: '99px',
      border: '1.5px solid currentColor', background: 'transparent',
      fontFamily: "'Inter',system-ui,sans-serif", fontSize: '11.5px', fontWeight: '700',
      cursor: 'pointer', verticalAlign: 'middle', opacity: '.55',
      transition: 'opacity .15s, background .15s, color .15s',
    });
    wrap.querySelector('svg').style.cssText = 'width:12px;height:12px;flex-shrink:0';
    wrap.addEventListener('mouseenter', () => { wrap.style.opacity = '1'; });
    wrap.addEventListener('mouseleave', () => {
      if (!localState.read.includes(key)) wrap.style.opacity = '.55';
    });
    wrap.addEventListener('click', e => {
      e.preventDefault(); e.stopPropagation();
      toggleRead(key);
    });
    heading.appendChild(wrap);
    refreshCheckUI(key);
  }

  /* ── 6. UI refreshers ─────────────────────────────────────────── */
  function refreshPillUI(key) {
    document.querySelectorAll(`.para-pill[data-para="${key}"]`).forEach(p => {
      p.classList.toggle('tb-read', localState.read.includes(key));
    });
  }
  function refreshCheckUI(key) {
    document.querySelectorAll(`.tb-readchk[data-para="${key}"]`).forEach(b => {
      const isRead = localState.read.includes(key);
      b.style.background = isRead ? 'rgba(16,185,129,.15)' : 'transparent';
      b.style.color      = isRead ? '#059669' : '';
      b.style.opacity    = isRead ? '1' : '.55';
      b.querySelector('span').textContent = isRead ? 'Прочитано' : 'Прочитано';
    });
  }
  function refreshAllUI() {
    localState.read.forEach(k => { refreshPillUI(k); refreshCheckUI(k); });
  }

  /* ── 7. Pill click → ВСЕГДА шлём last_para + mark_read одним POST
     Идемпотентно: на сервере `if (!arr.includes(mark_read)) arr.push(...)`
     не добавит дубликат. Эта «избыточность» лечит самовосстановлением
     ситуации, когда localState.read был засорён старым syncPending-багом
     (есть ключ локально, нет на сервере, mark_read иначе никогда не уйдёт). */
  function recordParaVisit(key) {
    if (!key) return;
    localState.last = key;
    if (!localState.read.includes(key)) {
      localState.read.push(key);
    }
    refreshPillUI(key);
    refreshCheckUI(key);
    persist();
    syncToServer({ mark_read: key });
  }

  function wirePillTracking() {
    // Hook 1: всплытие click → body. Работает в обычном HTML.
    document.body.addEventListener('click', e => {
      const pill = e.target && e.target.closest && e.target.closest('.para-pill[data-para]');
      if (!pill) return;
      recordParaVisit(pill.dataset.para);
    });
    // Hook 2: capture-фаза — ловит до того, как кто-то остановит propagation.
    document.addEventListener('click', e => {
      const pill = e.target && e.target.closest && e.target.closest('.para-pill[data-para]');
      if (!pill) return;
      if (pill.__tbVisited) return;
      pill.__tbVisited = true;
      setTimeout(() => { pill.__tbVisited = false; }, 100);
      recordParaVisit(pill.dataset.para);
    }, true);
    // Hook 3: monkey-patch setParaTab. chemistry-9 / physics-9 зовут её inline
    // через onclick="setParaTab('pN')" — перехват на уровне JS-функции
    // работает даже если event-стек сломан расширением или overlay.
    function patchSetParaTab() {
      if (typeof window.setParaTab !== 'function' || window.setParaTab.__tbPatched) return;
      const orig = window.setParaTab;
      const wrapped = function (para) {
        try {
          if (para && /^p\d+$/i.test(String(para))) recordParaVisit(String(para));
        } catch (e) {}
        return orig.apply(this, arguments);
      };
      wrapped.__tbPatched = true;
      window.setParaTab = wrapped;
    }
    patchSetParaTab();
    let tries = 0;
    const ivl = setInterval(() => {
      patchSetParaTab();
      if (typeof window.setParaTab === 'function' && window.setParaTab.__tbPatched) clearInterval(ivl);
      if (++tries > 20) clearInterval(ivl);
    }, 100);
    // Hook 4: справочник в chemistry-9 / physics-9 использует .tab[data-tab=refN]
    // (отдельная панель). Маппим ref<N> → p<N> и фиксируем как просмотр параграфа.
    document.addEventListener('click', e => {
      const tab = e.target && e.target.closest && e.target.closest('.tab[data-tab]');
      if (!tab) return;
      const m = String(tab.dataset.tab || '').match(/^ref(\d+)$/);
      if (!m) return;
      recordParaVisit('p' + m[1]);
    }, true);
    // Hook 5: polling — наблюдаем за классом .active на пилюлях.
    // Срабатывает на любую программную смену активного параграфа.
    let lastActivePara = null;
    setInterval(() => {
      const active = document.querySelector('.para-pill.active[data-para]');
      if (!active) return;
      const para = active.dataset.para;
      if (para && para !== lastActivePara) {
        lastActivePara = para;
        recordParaVisit(para);
      }
    }, 500);
  }

  /* ── 8. Inject styling for read-pills (subtle green dot) ─────── */
  function injectStyles() {
    const s = document.createElement('style');
    s.textContent = `
      .para-pill.tb-read { position: relative; }
      .para-pill.tb-read::after {
        content: ''; position: absolute; top: 3px; right: 3px;
        width: 6px; height: 6px; border-radius: 50%;
        background: #10b981; box-shadow: 0 0 0 1.5px rgba(255,255,255,.9);
      }
      @media (max-width: 600px) {
        #tb-back-btn span { display: none; }
        #tb-back-btn { padding: 7px 8px; }
      }
    `;
    document.head.appendChild(s);
  }

  /* ── 9a. Bookmarks (highlights/notes) ─────────────────────────── */
  let bookmarks = [];

  function loadBookmarks() {
    if (typeof LS === 'undefined' || !LS.getToken || !LS.getToken()) return Promise.resolve();
    return fetch('/api/textbooks/' + slug + '/bookmarks', {
      headers: { 'Authorization': 'Bearer ' + LS.getToken() },
    })
      .then(r => r.ok ? r.json() : { bookmarks: [] })
      .then(d => { bookmarks = d.bookmarks || []; })
      .catch(() => {});
  }

  function escHtml(s) {
    return String(s || '').replace(/[&<>"']/g, c => ({ '&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;' }[c]));
  }

  function installBookmarksBtn() {
    if (document.getElementById('tb-bm-btn')) return;
    const btn = document.createElement('button');
    btn.id = 'tb-bm-btn';
    btn.title = 'Мои закладки';
    btn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg>`;
    Object.assign(btn.style, {
      position: 'fixed', top: '10px', left: '125px', zIndex: '9999',
      width: '34px', height: '32px', border: 'none', borderRadius: '20px',
      background: 'rgba(0,0,0,.45)', color: '#fff',
      display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
      cursor: 'pointer', backdropFilter: 'blur(6px)',
      transition: 'background .15s, transform .12s',
      boxShadow: '0 2px 8px rgba(0,0,0,.18)',
    });
    btn.querySelector('svg').style.cssText = 'width:14px;height:14px';
    btn.addEventListener('mouseenter', () => { btn.style.background = 'rgba(0,0,0,.7)'; });
    btn.addEventListener('mouseleave', () => { btn.style.background = 'rgba(0,0,0,.45)'; });
    btn.addEventListener('click', toggleBookmarksPanel);
    document.body.appendChild(btn);
  }

  function installBookmarksPanel() {
    if (document.getElementById('tb-bm-panel')) return;
    const panel = document.createElement('div');
    panel.id = 'tb-bm-panel';
    Object.assign(panel.style, {
      position: 'fixed', top: '50px', left: '12px', zIndex: '9998',
      width: '320px', maxHeight: '60vh', overflowY: 'auto',
      background: '#fff', color: '#1c1917',
      border: '1px solid rgba(0,0,0,.12)', borderRadius: '12px',
      boxShadow: '0 8px 32px rgba(0,0,0,.25)',
      padding: '12px', display: 'none',
      fontFamily: "'Inter',system-ui,sans-serif", fontSize: '13px',
    });
    document.body.appendChild(panel);
  }

  function toggleBookmarksPanel() {
    const panel = document.getElementById('tb-bm-panel');
    if (panel.style.display === 'block') { panel.style.display = 'none'; return; }
    renderBookmarksPanel();
    panel.style.display = 'block';
  }

  function renderBookmarksPanel() {
    const panel = document.getElementById('tb-bm-panel');
    if (!panel) return;
    if (!bookmarks.length) {
      panel.innerHTML = `<div style="padding:14px;text-align:center;color:#78716c">
        Закладок нет<br><small>Выдели любой текст в учебнике и нажми «+ Закладка»</small>
      </div>`;
      return;
    }
    panel.innerHTML = `
      <div style="font-weight:800;font-size:11px;text-transform:uppercase;letter-spacing:.05em;color:#78716c;margin-bottom:8px;padding-bottom:8px;border-bottom:1px solid rgba(0,0,0,.08)">
        Мои закладки (${bookmarks.length})
      </div>
      ${bookmarks.map(b => {
        const colorMap = { yellow:'#fef08a', green:'#bbf7d0', blue:'#bfdbfe', pink:'#fbcfe8' };
        const borderMap= { yellow:'#ca8a04', green:'#16a34a', blue:'#2563eb', pink:'#db2777' };
        const bg = colorMap[b.color] || colorMap.yellow;
        const bd = borderMap[b.color] || borderMap.yellow;
        const paraLink = b.para ? `<a href="#${b.para}" style="text-decoration:none;color:${bd};font-weight:700;margin-right:6px">§${b.para.replace('p','')}</a>` : '';
        return `<div style="background:${bg};border-left:3px solid ${bd};padding:8px 10px;border-radius:6px;margin-bottom:7px;position:relative">
          ${paraLink}<span style="font-size:12.5px">«${escHtml(b.text)}»</span>
          ${b.note ? `<div style="margin-top:5px;color:#44403c;font-style:italic;font-size:12px">${escHtml(b.note)}</div>` : ''}
          <button onclick="window.__tbDeleteBookmark(${b.id})" style="position:absolute;top:5px;right:5px;border:none;background:none;cursor:pointer;color:#78716c;font-size:13px;line-height:1">×</button>
        </div>`;
      }).join('')}`;
  }

  window.__tbDeleteBookmark = function (id) {
    if (typeof LS === 'undefined' || !LS.getToken || !LS.getToken()) return;
    fetch('/api/textbooks/bookmarks/' + id, {
      method: 'DELETE',
      headers: { 'Authorization': 'Bearer ' + LS.getToken() },
    }).then(r => {
      if (r.ok) {
        bookmarks = bookmarks.filter(b => b.id !== id);
        renderBookmarksPanel();
      }
    }).catch(() => {});
  };

  /* Selection → "+ Закладка" floating button */
  function installSelectionHandler() {
    let btn = null;
    document.addEventListener('mouseup', () => {
      setTimeout(() => {
        const sel = window.getSelection();
        const text = (sel ? sel.toString() : '').trim();
        if (!text || text.length < 8 || text.length > 400) { hideBtn(); return; }
        if (sel.rangeCount === 0) { hideBtn(); return; }
        const rect = sel.getRangeAt(0).getBoundingClientRect();
        if (!rect || rect.width === 0) { hideBtn(); return; }
        showBtn(rect, text);
      }, 10);
    });
    document.addEventListener('mousedown', e => {
      if (e.target.closest('#tb-sel-btn')) return;
      hideBtn();
    });

    function showBtn(rect, text) {
      if (!btn) {
        btn = document.createElement('button');
        btn.id = 'tb-sel-btn';
        btn.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" style="width:13px;height:13px"><path d="M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/></svg><span>Закладка</span>`;
        Object.assign(btn.style, {
          position: 'fixed', zIndex: '10000',
          display: 'inline-flex', alignItems: 'center', gap: '5px',
          padding: '6px 11px', borderRadius: '6px',
          border: 'none', background: '#1c1917', color: '#fff',
          fontFamily: "'Inter',system-ui,sans-serif", fontSize: '12px', fontWeight: '700',
          cursor: 'pointer', boxShadow: '0 4px 14px rgba(0,0,0,.3)',
        });
        document.body.appendChild(btn);
      }
      btn.style.top  = (rect.top + window.scrollY - 36) + 'px';
      btn.style.left = (rect.left + window.scrollX + rect.width / 2 - 50) + 'px';
      btn.style.display = 'inline-flex';
      btn.onclick = () => createBookmarkFromSelection(text);
    }
    function hideBtn() { if (btn) btn.style.display = 'none'; }
  }

  function createBookmarkFromSelection(text) {
    if (typeof LS === 'undefined' || !LS.getToken || !LS.getToken()) {
      alert('Сохранение закладок доступно после входа в систему');
      return;
    }
    const note = prompt('Заметка к закладке (опционально):', '') || '';
    // Find current paragraph from selection
    const sel = window.getSelection();
    let para = null;
    if (sel.rangeCount > 0) {
      let node = sel.getRangeAt(0).startContainer;
      while (node && node !== document.body) {
        if (node.dataset?.para) { para = node.dataset.para; break; }
        if (node.id && /^p\d+$/.test(node.id)) { para = node.id; break; }
        if (node.id && /^ptab-p\d+$/.test(node.id)) { para = node.id.replace('ptab-', ''); break; }
        node = node.parentNode;
      }
    }
    if (!para) para = localState.last; // fallback

    fetch('/api/textbooks/' + slug + '/bookmarks', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer ' + LS.getToken(),
      },
      body: JSON.stringify({ text, note, para, color: 'yellow' }),
    })
      .then(r => r.ok ? r.json() : null)
      .then(d => {
        if (!d) return;
        bookmarks.unshift({ id: d.id, text, note, para, color: 'yellow', created_at: new Date().toISOString() });
        const btnEl = document.getElementById('tb-sel-btn');
        if (btnEl) btnEl.style.display = 'none';
        window.getSelection()?.removeAllRanges();
      });
  }

  /* ── 9. Boot ──────────────────────────────────────────────────── */
  function openParaByKey(key) {
    if (!key) return;
    if (typeof setParaTab === 'function') {
      try { setParaTab(key); return; } catch {}
    }
    const pill = document.querySelector(`.para-pill[data-para="${key}"]`);
    if (pill) pill.click();
  }

  function handleHashNav() {
    const m = (location.hash || '').match(/^#(p\d+)$/);
    if (m) {
      const key = m[1];
      openParaByKey(key);
      localState.last = key;
      if (!localState.read.includes(key)) {
        localState.read.push(key);
        refreshPillUI(key);
        refreshCheckUI(key);
      }
      persist();
      // Hash-вход (например, «Продолжить» из каталога) считается просмотром
      // параграфа — шлём last_para + mark_read одним POST.
      syncToServer({ mark_read: key });
      return true;
    }
    return false;
  }

  function boot() {
    injectStyles();
    installBackButton();
    installBookmarksBtn();
    installBookmarksPanel();
    installSelectionHandler();
    installReadCheckboxes();
    wirePillTracking();
    refreshAllUI();
    loadServerProgress();
    loadBookmarks();

    // Priority: URL hash > last visited paragraph
    if (!handleHashNav() && localState.last) {
      setTimeout(() => openParaByKey(localState.last), 50);
    }
    window.addEventListener('hashchange', handleHashNav);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', boot);
  } else {
    boot();
  }
})();
