(function () {
  'use strict';

  if (localStorage.getItem('tedProgressOff') === '1') return;
  if (window.__TED_PROGRESS_TARGET_TREE_V21__) return;
  window.__TED_PROGRESS_TARGET_TREE_V21__ = true;

  // &#9989; Цель
  var TARGET_NAME = 'Структуризация материально-финансового плана';

  // &#9989; (опционально) фиксируем по ID (самый надежный вариант)
  // Пример: var TARGET_IDS = ['123456'];
  var TARGET_IDS = null;

  // ---------- helpers: user key (чтобы не мешались разные аккаунты на одном компе) ----------
  function getUserKey(){
    try {
      var cands = [
        window.GC_USER_ID,
        window.gcUserId,
        window.gcAccountUserId,
        window.userId,
        window.user_id,
        window.accountUser && window.accountUser.id,
        window.userInfo && window.userInfo.id,
        document.body && document.body.dataset && (document.body.dataset.userId || document.body.dataset.userid)
      ];
      for (var i=0;i<cands.length;i++){
        var v = cands[i];
        if (v === 0) return '0';
        if (v && String(v).trim()) return String(v).trim();
      }
    } catch(e){}
    return 'anon';
  }
  function k(name){ return name + '::' + location.host + '::' + getUserKey(); }

  var KEYS = {
    progressCache: k('ted_progress_cache_target_tree_v21'),
    treeCache:     k('ted_tree_cache_target_tree_v21'),
    lessonVisit:   k('ted_lesson_visit_done_v1'),      // { [lessonId]: tsVisited }
    lessonVisitVer:k('ted_lesson_visit_done_v1_ver')   // int
  };

  var CFG = {
    maxDepth: 6,
    fetchConcurrency: 2,
    requestTimeoutMs: 15000,

    cacheTtlMs: 20 * 60 * 1000,

    ioRootMargin: '700px 0px',

    trainingPathRe: /\/teach\/control\/stream\/view\/id\/\d+/,
    lessonPathRe: /\/teach\/control\/lesson\/view\/id\/\d+/,

    // "жесткие" статусы GC (для заданий/стопов)
    hardDoneClasses: ['user-state-accomplished','user-state-answered','user-state-finished','user-state-completed'],

    // маркеры "есть задание"
    missionClass: 'user-state-has_mission',
    needAccomplishClass: 'user-state-need_accomplish',

    bannedClosestSelector: [
      '.gc-account-leftbar',
      'header','footer',
      '.breadcrumb','.breadcrumbs','.gc-breadcrumbs',
      '.nav','.nav-tabs','.tabs',
      '.dropdown','.dropdown-menu',
      '.btn-group',
      '[role="menu"]',
      '.modal','.popover'
    ].join(','),

    contentRootSelector: [
      '#gcAccountContent',
      '#content',
      '.gc-main-content',
      '.page-content',
      '.container',
      'body'
    ].join(','),

    text: {
      calculating: 'Считаю…',
      noLessons: 'Уроков пока нет',
      doneTraining: 'Тренинг завершён',
      doneLesson: 'Урок завершён',
      folderDone: 'Все уровни завершены',
      folderEmpty: 'Нет уровней',
      leftPrefix: 'Осталось: ',
      donePrefix: 'Пройдено: '
    },

    debug: false
  };

  function log(){ if (CFG.debug) console.log.apply(console, arguments); }
  function clamp(n,a,b){ return Math.max(a, Math.min(b, n)); }

  function normalizeText(s){
    return String(s || '')
      .toLowerCase()
      .replace(/\s+/g,' ')
      .replace(/[«»"']/g,'')
      .trim();
  }
  var TARGET_NORM = normalizeText(TARGET_NAME);

  function pluralRu(n, one, few, many) {
    n = Math.abs(n) % 100;
    var n1 = n % 10;
    if (n > 10 && n < 20) return many;
    if (n1 > 1 && n1 < 5) return few;
    if (n1 === 1) return one;
    return many;
  }
  function lessonWord(n){ return pluralRu(n,'урок','урока','уроков'); }
  function levelWord(n){ return pluralRu(n,'уровень','уровня','уровней'); }

  function canonicalTrainingUrl(url, base) {
    try {
      var u = new URL(url, base || location.href);
      u.hash = ''; u.search = '';
      var m = u.pathname.match(CFG.trainingPathRe);
      if (m) u.pathname = m[0];
      return u.toString();
    } catch (e) {
      var s = String(url || '').split('#')[0].split('?')[0];
      var m2 = s.match(CFG.trainingPathRe);
      return m2 ? m2[0] : s;
    }
  }

  function canonicalLessonUrl(url, base) {
    try {
      var u = new URL(url, base || location.href);
      u.hash = ''; u.search = '';
      var m = u.pathname.match(CFG.lessonPathRe);
      if (m) u.pathname = m[0];
      return u.toString();
    } catch (e) {
      var s = String(url || '').split('#')[0].split('?')[0];
      var m2 = s.match(CFG.lessonPathRe);
      return m2 ? m2[0] : s;
    }
  }

  function idFromUrl(url){
    var m = String(url).match(/\/id\/(\d+)/);
    return m ? m[1] : String(url);
  }

  function resolveHref(href, baseUrl){
    try { return new URL(href, baseUrl).toString(); } catch(e){ return null; }
  }

  function inBannedUI(node){
    return node && node.closest && node.closest(CFG.bannedClosestSelector);
  }

  function getContentRoot(doc){
    doc = doc || document;
    return doc.querySelector(CFG.contentRootSelector) || doc.body;
  }

  function currentStreamId(){
    var m = location.pathname.match(/\/teach\/control\/stream\/view\/id\/(\d+)/);
    return m ? m[1] : null;
  }
  function currentLessonId(){
    var m = location.pathname.match(/\/teach\/control\/lesson\/view\/id\/(\d+)/);
    return m ? m[1] : null;
  }

  // ---------- localStorage JSON helpers ----------
  function loadLS(key){
    try { return JSON.parse(localStorage.getItem(key) || '{}'); } catch(e){ return {}; }
  }
  function saveLS(key, obj){
    try { localStorage.setItem(key, JSON.stringify(obj)); } catch(e){}
  }

  // ---------- visit version (для авто-обновления прогресса) ----------
  function getVisitVer(){
    var v = parseInt(localStorage.getItem(KEYS.lessonVisitVer) || '0', 10);
    return isFinite(v) ? v : 0;
  }
  function bumpVisitVer(){
    var v = getVisitVer() + 1;
    try { localStorage.setItem(KEYS.lessonVisitVer, String(v)); } catch(e){}
    return v;
  }

  // ---------- MARK DONE "по факту входа" ----------
  function markLessonVisitedIfLessonPage(){
    if (!CFG.lessonPathRe.test(location.pathname)) return;
    var lessonId = currentLessonId();
    if (!lessonId) return;

    var store = loadLS(KEYS.lessonVisit);
    if (store[String(lessonId)]) return; // уже отмечено

    store[String(lessonId)] = Date.now();
    saveLS(KEYS.lessonVisit, store);

    // инвалидация кэша прогресса
    bumpVisitVer();
  }

  // запускаем сразу (важно: скрипт должен быть подключён и на страницах уроков)
  markLessonVisitedIfLessonPage();

  function isLessonVisited(lessonId){
    if (!lessonId) return false;
    var store = loadLS(KEYS.lessonVisit);
    return !!store[String(lessonId)];
  }

  // ---------- CSS ----------
  function injectCss(){
    if (document.getElementById('ted-trprogress-style-target-v21')) return;
    var css =
      '.ted-trprogress{margin:8px 0 0;max-width:760px;padding:10px 12px;background:#fff;border:1px solid rgba(15,23,42,.10);border-radius:12px;box-shadow:0 6px 16px rgba(15,23,42,.05)}' +
      '.ted-trprogress__top{display:flex;align-items:baseline;justify-content:space-between;gap:12px;font-size:12px;color:#64748B}' +
      '.ted-trprogress__nums{font-weight:750;color:#0F172A;white-space:nowrap}' +
      '.ted-trprogress__bar{margin-top:8px;height:10px;background:rgba(15,23,42,.08);border-radius:999px;overflow:hidden}' +
      '.ted-trprogress__bar>i{display:block;height:100%;width:0%;background:linear-gradient(90deg,#4F46E5,#06B6D4);transition:width .6s ease;border-radius:999px}';
    var st = document.createElement('style');
    st.id = 'ted-trprogress-style-target-v21';
    st.textContent = css;
    document.head.appendChild(st);
  }

  // ---------- cache helpers ----------
  function getCached(cacheObj, id){
    var v = cacheObj[id];
    if (!v) return null;
    if ((Date.now() - v.ts) > CFG.cacheTtlMs) return null;

    var payload = v.payload || null;
    if (!payload) return null;

    // &#9989; если после последнего расчёта изменился visitVer — пересчитываем
    if (payload._visitVer !== getVisitVer()) return null;

    return payload;
  }

  function setCached(cacheObj, key, id, payload){
    cacheObj[id] = { payload: payload, ts: Date.now() };
    saveLS(key, cacheObj);
  }

  // ---------- UI ----------
  function makeUI(){
    var wrap = document.createElement('div');
    wrap.className = 'ted-trprogress';
    wrap.innerHTML =
      '<div class="ted-trprogress__top">' +
      '  <span class="ted-trprogress__state">' + CFG.text.calculating + '</span>' +
      '  <span class="ted-trprogress__nums">—</span>' +
      '</div>' +
      '<div class="ted-trprogress__bar"><i></i></div>';
    return wrap;
  }

  function setCalculating(ui){
    ui.querySelector('.ted-trprogress__state').textContent = CFG.text.calculating;
    ui.querySelector('.ted-trprogress__nums').textContent = '—';
    ui.querySelector('.ted-trprogress__bar > i').style.width = '0%';
  }

  function renderLeaf(ui, done, total){
    done = Math.max(0, done);
    total = Math.max(0, total);
    if (total > 0) done = clamp(done, 0, total);

    var left = Math.max(0, total - done);
    var pct = total > 0 ? (done / total) * 100 : 0;
    pct = clamp(pct, 0, 100);

    if (total === 0) {
      ui.querySelector('.ted-trprogress__state').textContent = CFG.text.noLessons;
      ui.querySelector('.ted-trprogress__nums').textContent = '—';
      ui.querySelector('.ted-trprogress__bar > i').style.width = '0%';
      return;
    }

    ui.querySelector('.ted-trprogress__nums').textContent =
      CFG.text.donePrefix + done + ' ' + lessonWord(done) + ' из ' + total;

    ui.querySelector('.ted-trprogress__state').textContent =
      left === 0 ? (total === 1 ? CFG.text.doneLesson : CFG.text.doneTraining)
                : (CFG.text.leftPrefix + left + ' ' + lessonWord(left));

    ui.querySelector('.ted-trprogress__bar > i').style.width = pct.toFixed(2) + '%';
  }

  function renderFolder(ui, doneLevels, totalLevels){
    doneLevels = Math.max(0, doneLevels);
    totalLevels = Math.max(0, totalLevels);
    if (totalLevels > 0) doneLevels = clamp(doneLevels, 0, totalLevels);

    var left = Math.max(0, totalLevels - doneLevels);
    var pct = totalLevels > 0 ? (doneLevels / totalLevels) * 100 : 0;
    pct = clamp(pct, 0, 100);

    if (totalLevels === 0) {
      ui.querySelector('.ted-trprogress__state').textContent = CFG.text.folderEmpty;
      ui.querySelector('.ted-trprogress__nums').textContent = '—';
      ui.querySelector('.ted-trprogress__bar > i').style.width = '0%';
      return;
    }

    ui.querySelector('.ted-trprogress__nums').textContent =
      CFG.text.donePrefix + doneLevels + ' ' + levelWord(doneLevels) + ' из ' + totalLevels;

    ui.querySelector('.ted-trprogress__state').textContent =
      left === 0 ? CFG.text.folderDone
                : (CFG.text.leftPrefix + left + ' ' + levelWord(left));

    ui.querySelector('.ted-trprogress__bar > i').style.width = pct.toFixed(2) + '%';
  }

  // ---------- status detection ----------
  function hasAnyClass(el, arr){
    if (!el || !el.classList) return false;
    for (var i=0;i<arr.length;i++) if (el.classList.contains(arr[i])) return true;
    return false;
  }

  function getStateCarrier(el){
    if (!el) return el;
    var carrier = el.closest ? (el.closest('li, tr, .lesson') || el) : el;
    var cn = String(carrier.className || '');
    if (cn.indexOf('user-state-') !== -1) return carrier;
    var inner = carrier.querySelector ? carrier.querySelector('[class*="user-state-"]') : null;
    return inner || carrier;
  }

  function isMissionLesson(carrier){
    if (!carrier || !carrier.classList) return false;
    return carrier.classList.contains(CFG.missionClass) || carrier.classList.contains(CFG.needAccomplishClass);
  }

  // &#9989; Новая логика done:
  // - если урок с заданием/стопом => done только по hardDoneClasses
  // - если обычный урок => done по hardDoneClasses ИЛИ по факту входа (visited)
  function isLessonDone(carrier, lessonId){
    if (carrier && isMissionLesson(carrier)) {
      return hasAnyClass(carrier, CFG.hardDoneClasses);
    }
    if (carrier && hasAnyClass(carrier, CFG.hardDoneClasses)) return true;
    return isLessonVisited(lessonId);
  }

  function extractLessonLinks(doc, baseUrl){
    var root = getContentRoot(doc);
    var anchors = Array.from(root.querySelectorAll('a[href]'));
    var out = [];

    anchors.forEach(function(a){
      if (inBannedUI(a)) return;

      var abs = resolveHref(a.getAttribute('href'), baseUrl);
      if (!abs) return;

      var canon = canonicalLessonUrl(abs, baseUrl);
      if (!CFG.lessonPathRe.test(canon)) return;

      out.push({ a:a, url:canon });
    });

    var seen = new Set();
    return out.filter(function(x){
      if (seen.has(x.url)) return false;
      seen.add(x.url);
      return true;
    });
  }

  function countLessons(doc, baseUrl){
    var links = extractLessonLinks(doc, baseUrl);
    if (!links.length) return { total: 0, done: 0 };

    var total = links.length, done = 0;
    links.forEach(function(x){
      var lessonId = idFromUrl(x.url);
      var host = getStateCarrier(x.a);
      if (isLessonDone(host, lessonId)) done++;
    });
    done = clamp(done, 0, total);
    return { total: total, done: done };
  }

  function extractChildTrainings(doc, baseUrl){
    var root = getContentRoot(doc);
    var anchors = Array.from(root.querySelectorAll('a[href]'));

    var baseCanon = canonicalTrainingUrl(baseUrl, baseUrl);
    var baseId = idFromUrl(baseCanon);

    var byId = new Map();
    anchors.forEach(function(a){
      if (inBannedUI(a)) return;

      var abs = resolveHref(a.getAttribute('href'), baseUrl);
      if (!abs) return;

      var canon = canonicalTrainingUrl(abs, baseUrl);
      if (!CFG.trainingPathRe.test(canon)) return;

      var id = idFromUrl(canon);
      if (id === baseId) return;

      if (!byId.has(id)) byId.set(id, canon);
    });

    return Array.from(byId.values());
  }

  async function fetchDoc(url){
    var controller = (typeof AbortController !== 'undefined') ? new AbortController() : null;
    var timer = null;
    try {
      if (controller) {
        timer = setTimeout(function(){ try { controller.abort(); } catch(e){} }, CFG.requestTimeoutMs);
      }
      var resp = await fetch(url, { credentials: 'include', signal: controller ? controller.signal : undefined });
      if (!resp.ok) throw new Error('HTTP ' + resp.status);
      var html = await resp.text();
      return new DOMParser().parseFromString(html, 'text/html');
    } finally {
      if (timer) clearTimeout(timer);
    }
  }

  function isComplete(info){
    if (!info) return false;
    if (info.type === 'leaf') return info.totalLessons > 0 && info.doneLessons === info.totalLessons;
    if (info.type === 'folder') return info.totalLevels > 0 && info.doneLevels === info.totalLevels;
    return false;
  }

  // --------- target detection ----------
  function cardTextForAnchor(a){
    var card = a.closest && a.closest('li, .stream, .stream-row, .stream-item, .gc-stream, .list-item, .training, .training-item');
    return normalizeText((card ? card.innerText : a.innerText) || '');
  }

  function isTargetByName(anchor){
    return cardTextForAnchor(anchor).indexOf(TARGET_NORM) !== -1;
  }

  function isTargetById(url){
    if (!Array.isArray(TARGET_IDS) || !TARGET_IDS.length) return false;
    return TARGET_IDS.indexOf(String(idFromUrl(url))) !== -1;
  }

  function findTargetRootUrlOnPage(){
    if (Array.isArray(TARGET_IDS) && TARGET_IDS.length) {
      return location.origin + '/teach/control/stream/view/id/' + TARGET_IDS[0];
    }

    var root = getContentRoot(document);
    var anchors = Array.from(root.querySelectorAll('a[href]'));
    var best = null;

    anchors.forEach(function(a){
      if (inBannedUI(a)) return;

      var abs = resolveHref(a.getAttribute('href'), location.href);
      if (!abs) return;

      var canon = canonicalTrainingUrl(abs, location.href);
      if (!CFG.trainingPathRe.test(canon)) return;

      if (!isTargetByName(a)) return;
      best = best || canon;
    });

    if (best) return best;

    var pageTxt = normalizeText((document.querySelector('h1') && document.querySelector('h1').innerText) || document.title || '');
    if (pageTxt.indexOf(TARGET_NORM) !== -1 && CFG.trainingPathRe.test(location.pathname)) {
      return canonicalTrainingUrl(location.href, location.href);
    }

    return null;
  }

  // --------- tree cache ----------
  function loadTreeCache(){
    var t = loadLS(KEYS.treeCache);
    if (!t || !t.ts || (Date.now() - t.ts) > CFG.cacheTtlMs) return null;
    if (!t.rootId || !t.ids) return null;
    return t;
  }

  function saveTreeCache(rootId, idsSet){
    saveLS(KEYS.treeCache, {
      ts: Date.now(),
      rootId: String(rootId),
      ids: Array.from(idsSet)
    });
  }

  async function buildTree(rootUrl){
    var rootCanon = canonicalTrainingUrl(rootUrl, rootUrl);
    var rootId = idFromUrl(rootCanon);

    var ids = new Set([String(rootId)]);
    var seen = new Set([String(rootId)]);

    var q = [{ url: rootCanon, depth: 0 }];

    while (q.length) {
      var cur = q.shift();
      if (cur.depth >= CFG.maxDepth) continue;

      try {
        var doc = await fetchDoc(cur.url);
        var children = extractChildTrainings(doc, cur.url);

        for (var i=0;i<children.length;i++){
          var cUrl = canonicalTrainingUrl(children[i], children[i]);
          var cId = String(idFromUrl(cUrl));
          if (!ids.has(cId)) ids.add(cId);

          if (!seen.has(cId)) {
            seen.add(cId);
            q.push({ url: cUrl, depth: cur.depth + 1 });
          }
        }
      } catch(e) {
        log('[TED TREE] buildTree error', cur.url, e);
      }
    }

    saveTreeCache(rootId, ids);
    return { rootUrl: rootCanon, rootId: String(rootId), ids: ids };
  }

  function isInsideTargetTree(tree){
    if (!tree) return false;
    var curId = currentStreamId();
    if (!curId) return false;
    return tree.ids.indexOf(String(curId)) !== -1;
  }

  // --------- progress calc ----------
  var inFlightAgg = new Map();

  async function analyzeTrainingProgress(url, depth, visited, progressCache){
    var canonUrl = canonicalTrainingUrl(url, url);
    var id = String(idFromUrl(canonUrl));

    if (visited.has(id)) {
      return { type:'empty', doneLessons:0, totalLessons:0, doneLevels:0, totalLevels:0, _visitVer: getVisitVer() };
    }
    visited.add(id);

    var cached = getCached(progressCache, id);
    if (cached) return cached;

    if (inFlightAgg.has(id)) return await inFlightAgg.get(id);

    var p = (async function(){
      try{
        var doc = await fetchDoc(canonUrl);

        var ls = countLessons(doc, canonUrl);
        if (ls.total > 0) {
          var leaf = { type:'leaf', doneLessons: ls.done, totalLessons: ls.total, doneLevels:0, totalLevels:0, _visitVer: getVisitVer() };
          setCached(progressCache, KEYS.progressCache, id, leaf);
          return leaf;
        }

        if (depth < CFG.maxDepth) {
          var kids = extractChildTrainings(doc, canonUrl);
          if (kids.length > 0) {
            var doneLevels = 0;
            for (var i=0;i<kids.length;i++){
              var child = await analyzeTrainingProgress(kids[i], depth+1, visited, progressCache);
              if (isComplete(child)) doneLevels++;
            }
            var folder = { type:'folder', doneLessons:0, totalLessons:0, doneLevels: doneLevels, totalLevels: kids.length, _visitVer: getVisitVer() };
            setCached(progressCache, KEYS.progressCache, id, folder);
            return folder;
          }
        }

        var empty = { type:'empty', doneLessons:0, totalLessons:0, doneLevels:0, totalLevels:0, _visitVer: getVisitVer() };
        setCached(progressCache, KEYS.progressCache, id, empty);
        return empty;

      } finally {
        inFlightAgg.delete(id);
      }
    })();

    inFlightAgg.set(id, p);
    return await p;
  }

  // --------- IO queue ----------
  var queue = [];
  var active = 0;

  function enqueueJob(job){
    if (job.ui.__tedBusy) return;
    job.ui.__tedBusy = true;
    queue.push(job);
    pump();
  }

  function pump(){
    while (active < CFG.fetchConcurrency) {
      var job = queue.shift();
      if (!job) return;

      active++;
      (async function(j){
        try{
          var progressCache = loadLS(KEYS.progressCache);
          var visited = new Set();
          var info = await analyzeTrainingProgress(j.url, 0, visited, progressCache);

          if (!info || info.type === 'empty') renderLeaf(j.ui, 0, 0);
          else if (info.type === 'leaf') renderLeaf(j.ui, info.doneLessons, info.totalLessons);
          else if (info.type === 'folder') renderFolder(j.ui, info.doneLevels, info.totalLevels);
          else renderLeaf(j.ui, 0, 0);

        } catch(e){
          log('[TED JOB] error', j.url, e);
        } finally {
          j.ui.__tedBusy = false;
          j.ui.__tedLoaded = true;
          active--;
          pump();
        }
      })(job);
    }
  }

  function setupIO(){
    if (!('IntersectionObserver' in window)) return null;
    return new IntersectionObserver(function(entries, obs){
      entries.forEach(function(e){
        if (!e.isIntersecting) return;
        var ui = e.target;
        obs.unobserve(ui);

        if (ui.__tedLoaded) return;
        if (!ui.__tedUrl) return;

        enqueueJob({ url: ui.__tedUrl, ui: ui });
      });
    }, { root:null, rootMargin: CFG.ioRootMargin, threshold: 0.01 });
  }

  // --------- mount bars ----------
  function mountBarAfterAnchor(io, a, url){
    var next = a.nextElementSibling;
    var ui = (next && next.classList && next.classList.contains('ted-trprogress')) ? next : null;

    if (!ui) {
      ui = makeUI();
      a.insertAdjacentElement('afterend', ui);
      setCalculating(ui); // только при создании — анти-миг
    }

    ui.__tedUrl = url;

    if (io && io.observe && !ui.__tedObserved) {
      ui.__tedObserved = true;
      io.observe(ui);
    }
  }

  function mountOnlyTargetOnList(io){
    var root = getContentRoot(document);
    var anchors = Array.from(root.querySelectorAll('a[href]'));
    var best = null;

    anchors.forEach(function(a){
      if (inBannedUI(a)) return;

      var abs = resolveHref(a.getAttribute('href'), location.href);
      if (!abs) return;

      var canon = canonicalTrainingUrl(abs, location.href);
      if (!CFG.trainingPathRe.test(canon)) return;

      if (isTargetById(canon) || isTargetByName(a)) {
        best = { a:a, url:canon };
      }
    });

    if (!best) return;
    mountBarAfterAnchor(io, best.a, best.url);
  }

  function mountPartsOnStreamPage(io){
    if (!CFG.trainingPathRe.test(location.pathname)) return;

    var root = getContentRoot(document);
    var anchors = Array.from(root.querySelectorAll('a[href]'));

    var selfId = String(currentStreamId() || '');
    var bestById = new Map();

    anchors.forEach(function(a){
      if (inBannedUI(a)) return;

      var abs = resolveHref(a.getAttribute('href'), location.href);
      if (!abs) return;

      var canon = canonicalTrainingUrl(abs, location.href);
      if (!CFG.trainingPathRe.test(canon)) return;

      var id = String(idFromUrl(canon));
      if (!id || id === selfId) return;

      if (a.closest && a.closest('.btn, .button, .btn-group, .dropdown, .dropdown-menu')) return;
      if (!bestById.has(id)) bestById.set(id, { a:a, url:canon });
    });

    bestById.forEach(function(x){
      mountBarAfterAnchor(io, x.a, x.url);
    });
  }

  // --------- авто-обновление прогресса (после посещения уроков) ----------
  var lastSeenVisitVer = getVisitVer();

  function recomputeAllBarsIfVisitChanged(){
    var v = getVisitVer();
    if (v === lastSeenVisitVer) return;
    lastSeenVisitVer = v;

    // пересчитать все уже вставленные бары (без "Считаю…" чтобы не мигало)
    var bars = document.querySelectorAll('.ted-trprogress');
    Array.from(bars).forEach(function(ui){
      if (!ui.__tedUrl) return;
      ui.__tedLoaded = false;
      enqueueJob({ url: ui.__tedUrl, ui: ui });
    });
  }

  window.addEventListener('visibilitychange', function(){
    if (document.visibilityState === 'visible') {
      recomputeAllBarsIfVisitChanged();
    }
  });

  // если урок открыли в другой вкладке — тут прилетит storage event
  window.addEventListener('storage', function(e){
    if (!e) return;
    if (e.key === KEYS.lessonVisitVer) {
      recomputeAllBarsIfVisitChanged();
    }
  });

  // --------- init / re-init ----------
  var io = null;
  var initScheduled = false;

  async function init(){
    // На страницах уроков ничего не рисуем (там только отметка "посетил")
    if (CFG.lessonPathRe.test(location.pathname)) return;

    injectCss();
    if (!io) io = setupIO();

    var rootUrl = findTargetRootUrlOnPage();

    if (rootUrl) {
      var tree = loadTreeCache();
      var rootId = String(idFromUrl(rootUrl));
      if (!tree || tree.rootId !== rootId) {
        buildTree(rootUrl).catch(function(e){ log('[TED TREE] buildTree failed', e); });
      }
    }

    mountOnlyTargetOnList(io);

    var tree2 = loadTreeCache();
    var inside = isInsideTargetTree(tree2);

    if (!inside) {
      var h = normalizeText((document.querySelector('h1') && document.querySelector('h1').innerText) || document.title || '');
      if (h.indexOf(TARGET_NORM) !== -1) inside = true;
    }

    if (inside) {
      mountPartsOnStreamPage(io);
    }

    // сразу попробуем обновиться (если версию уже меняли)
    recomputeAllBarsIfVisitChanged();
  }

  function scheduleInit(){
    if (initScheduled) return;
    initScheduled = true;
    setTimeout(function(){
      initScheduled = false;
      try { init(); } catch(e) { console.warn('[TED TARGET TREE v2.1] init failed', e); }
    }, 250);
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', scheduleInit);
  } else {
    scheduleInit();
  }
  window.addEventListener('load', scheduleInit);

  try {
    var mo = new MutationObserver(scheduleInit);
    mo.observe(document.documentElement || document.body, { childList:true, subtree:true });
  } catch(e){}

  window.TEDTargetTreeProgress = {
    off: function(){ localStorage.setItem('tedProgressOff','1'); location.reload(); },
    on: function(){ localStorage.removeItem('tedProgressOff'); location.reload(); },
    clearCache: function(){
      try { localStorage.removeItem(KEYS.progressCache); } catch(e){}
      try { localStorage.removeItem(KEYS.treeCache); } catch(e){}
      console.log('[TED TARGET TREE v2.1] caches cleared');
    },
    clearVisits: function(){
      try { localStorage.removeItem(KEYS.lessonVisit); } catch(e){}
      try { localStorage.removeItem(KEYS.lessonVisitVer); } catch(e){}
      console.log('[TED TARGET TREE v2.1] visits cleared');
    }
  };

})();