/**
 * Дерево условий восприятия (Perception Tree).
 *
 * Иерархия из 6 уровней:
 *   Level 1: BaseID (1=Плохо, 2=Норма, 3=Хорошо)
 *   Level 2: EmotionID (эмоция — сочетание активных базовых контекстов)
 *   Level 3: ActivityID — ID образа из FinalImages (global.FinalImages_findFinalImageId / getOrCreateFinalImageId), не ID кнопки стимула
 *   Level 4: ToneMoodID (тон и настроение сообщения)
 *   Level 5: SimbolID (первый символ первой фразы)
 *   Level 6: PhraseID (ID распознанной фразы)
 *
 * У каждого узла: id, parentId и значения по уровням (baseID..phraseID).
 * Быстрый поиск ветки: по текущим 6 ID сразу находится узел (pathIndex).
 * По любому nodeId можно восстановить полный путь (все 6 ID узла).
 *
 * Компактный формат в памяти: одна строка на узел
 *   ID|ParentNode|BaseID|EmotionID|ActivityID|ToneMoodID|SimbolID|PhraseID
 *
 * Сохранение: IndexedDB (авто) и файл (Сохранить/Загрузить).
 */
(function (global) {
  'use strict';

  var LEVEL_NAMES = ['BaseID', 'EmotionID', 'ActivityID', 'ToneMoodID', 'SimbolID', 'PhraseID'];

  /** Все узлы: id -> { id, parentId, baseID, emotionID, activityID, toneMoodID, simbolID, phraseID } */
  var nodes = new Map();

  /** Индекс: ключ пути (6 значений через "|") -> nodeId. Обеспечивает O(1) поиск ветки по активным условиям. */
  var pathIndex = new Map();

  /** Следующий свободный id узла. */
  var nextNodeId = 1;

  /** Корневой родитель (уровень 0). */
  var ROOT_PARENT = 0;

  function pathKey(baseID, emotionID, activityID, toneMoodID, simbolID, phraseID) {
    return (baseID || 0) + '|' + (emotionID || 0) + '|' + (activityID || 0) + '|' +
      (toneMoodID || 0) + '|' + (simbolID || 0) + '|' + (phraseID || 0);
  }

  function pathKeyFromArray(arr) {
    if (!Array.isArray(arr) || arr.length < 6) {
      return pathKey(0, 0, 0, 0, 0, 0);
    }
    return pathKey(arr[0], arr[1], arr[2], arr[3], arr[4], arr[5]);
  }

  /**
   * Добавить узел в дерево.
   * @param {number} parentId — id родителя (0 для корневого уровня).
   * @param {number} baseID — 1/2/3 (Плохо/Норма/Хорошо).
   * @param {number} emotionID
   * @param {number} activityID
   * @param {number} toneMoodID
   * @param {number} simbolID
   * @param {number} phraseID
   * @returns {number} id созданного узла.
   */
  function addNode(parentId, baseID, emotionID, activityID, toneMoodID, simbolID, phraseID) {
    var id = nextNodeId++;
    var b = baseID == null ? 0 : baseID;
    var e = emotionID == null ? 0 : emotionID;
    var a = activityID == null ? 0 : activityID;
    var t = toneMoodID == null ? 0 : toneMoodID;
    var s = simbolID == null ? 0 : simbolID;
    var p = phraseID == null ? 0 : phraseID;
    var node = {
      id: id,
      parentId: parentId == null ? ROOT_PARENT : parentId,
      baseID: b,
      emotionID: e,
      activityID: a,
      toneMoodID: t,
      simbolID: s,
      phraseID: p
    };
    nodes.set(id, node);
    pathIndex.set(pathKey(b, e, a, t, s, p), id);
    scheduleTreeSave();
    return id;
  }

  /**
   * Найти активную ветку по текущим условиям (все 6 ID).
   * Как только компоненты узлов установлены, ветка сразу определяется.
   * @returns {number|null} id узла или null.
   */
  function findActiveBranch(baseID, emotionID, activityID, toneMoodID, simbolID, phraseID) {
    var key = pathKey(
      baseID == null ? 0 : baseID,
      emotionID == null ? 0 : emotionID,
      activityID == null ? 0 : activityID,
      toneMoodID == null ? 0 : toneMoodID,
      simbolID == null ? 0 : simbolID,
      phraseID == null ? 0 : phraseID
    );
    var nodeId = pathIndex.get(key);
    return nodeId !== undefined ? nodeId : null;
  }

  /**
   * Найти активную ветку по массиву из 6 ID [baseID, emotionID, activityID, toneMoodID, simbolID, phraseID].
   */
  function findActiveBranchByArray(pathArr) {
    if (!Array.isArray(pathArr) || pathArr.length < 6) return null;
    return findActiveBranch(pathArr[0], pathArr[1], pathArr[2], pathArr[3], pathArr[4], pathArr[5]);
  }

  /**
   * По конечному id узла восстановить полный путь (все 6 уровней).
   * @param {number} nodeId
   * @returns {number[]|null} [baseID, emotionID, activityID, toneMoodID, simbolID, phraseID] или null.
   */
  function getPathByNodeId(nodeId) {
    var node = nodes.get(nodeId);
    if (!node) return null;
    return [node.baseID, node.emotionID, node.activityID, node.toneMoodID, node.simbolID, node.phraseID];
  }

  /**
   * Получить узел по id.
   */
  function getNode(nodeId) {
    return nodes.get(nodeId) || null;
  }

  /**
   * Дети узла (по parentId).
   */
  function getChildren(nodeId) {
    var list = [];
    nodes.forEach(function (n) {
      if (n.parentId === nodeId) list.push(n);
    });
    return list;
  }

  /**
   * Получить или создать конечный узел для пути из 6 уровней.
   * Создаёт узлы по уровням, если их ещё нет.
   * @param {number[]} pathArr — [baseID, emotionID, activityID, toneMoodID, simbolID, phraseID]
   * @returns {number} id конечного узла.
   */
  function getOrCreateTerminalNode(pathArr) {
    var path = [];
    for (var i = 0; i < 6; i++) {
      path.push(Array.isArray(pathArr) && pathArr[i] != null ? pathArr[i] : 0);
    }
    var parentId = ROOT_PARENT;
    for (var lev = 0; lev < 6; lev++) {
      var b = path[0];
      var e = lev >= 1 ? path[1] : 0;
      var a = lev >= 2 ? path[2] : 0;
      var t = lev >= 3 ? path[3] : 0;
      var s = lev >= 4 ? path[4] : 0;
      var p = lev >= 5 ? path[5] : 0;
      var key = pathKey(b, e, a, t, s, p);
      var nodeId = pathIndex.get(key);
      if (nodeId === undefined) {
        nodeId = addNode(parentId, b, e, a, t, s, p);
      }
      parentId = nodeId;
    }
    return parentId;
  }

  /**
   * Текущий активный терминальный узел дерева по состоянию виталов и активных стимулов.
   * Используется для привязки автоматизмов и проверки при осознании.
   * @returns {number} id узла или 0
   */
  function getActiveTerminalNodeId() {
    var baseID = global.BadNormWell || 0;
    var emotionID = 0;
    if (typeof global.Emotions_getOrCreateEmotionId === 'function' && Array.isArray(global.BasicContextsActived)) {
      emotionID = global.Emotions_getOrCreateEmotionId(global.BasicContextsActived) || 0;
    }
    var activityID = 0;
    if (typeof global.FinalImages_getOrCreateFinalImageId === 'function') {
      var activeIds = typeof global.StimulusGlobalId_getActiveGlobalIds === 'function'
        ? global.StimulusGlobalId_getActiveGlobalIds()
        : [];
      if (activeIds.length) activityID = global.FinalImages_getOrCreateFinalImageId(activeIds) || 0;
    }
    var nodeId = findActiveBranch(baseID, emotionID, activityID, 0, 0, 0);
    if (nodeId == null) nodeId = getOrCreateTerminalNode([baseID, emotionID, activityID, 0, 0, 0]);
    return nodeId != null ? nodeId : 0;
  }

  // ---------- Компактная сериализация (формат как в OLD_CODE/automatizm_tree.txt) ----------

  function serializeCompact() {
    var lines = [];
    nodes.forEach(function (node) {
      lines.push(
        node.id + '|' + node.parentId + '|' + node.baseID + '|' + node.emotionID + '|' +
        node.activityID + '|' + node.toneMoodID + '|' + node.simbolID + '|' + node.phraseID
      );
    });
    return lines.join('\n');
  }

  function deserializeCompact(text) {
    nodes.clear();
    pathIndex.clear();
    var maxId = 0;
    if (!text || typeof text !== 'string') {
      nextNodeId = 1;
      return;
    }
    var lines = text.split(/\r?\n/);
    for (var i = 0; i < lines.length; i++) {
      var line = lines[i].trim();
      if (!line) continue;
      var parts = line.split('|');
      if (parts.length < 8) continue;
      var id = parseInt(parts[0], 10);
      var parentId = parseInt(parts[1], 10);
      var baseID = parseInt(parts[2], 10) || 0;
      var emotionID = parseInt(parts[3], 10) || 0;
      var activityID = parseInt(parts[4], 10) || 0;
      var toneMoodID = parseInt(parts[5], 10) || 0;
      var simbolID = parseInt(parts[6], 10) || 0;
      var phraseID = parseInt(parts[7], 10) || 0;
      if (isNaN(id)) continue;
      var node = {
        id: id,
        parentId: isNaN(parentId) ? ROOT_PARENT : parentId,
        baseID: baseID,
        emotionID: emotionID,
        activityID: activityID,
        toneMoodID: toneMoodID,
        simbolID: simbolID,
        phraseID: phraseID
      };
      nodes.set(id, node);
      pathIndex.set(pathKey(baseID, emotionID, activityID, toneMoodID, simbolID, phraseID), id);
      if (id > maxId) maxId = id;
    }
    nextNodeId = maxId + 1;
  }

  /**
   * Состояние для сохранения (объект: compactText + nextNodeId для целостности).
   */
  function serializeState() {
    return {
      compact: serializeCompact(),
      nextId: nextNodeId
    };
  }

  function applyState(state) {
    if (!state) return;
    if (state.compact) deserializeCompact(state.compact);
    if (typeof state.nextId === 'number' && state.nextId > 0) {
      nextNodeId = state.nextId;
    }
  }

  // ---------- IndexedDB ----------

  var treeDb = null;
  var TREE_DB_NAME = 'BEAST_PerceptionTree_DB';
  var TREE_DB_VERSION = 1;
  var TREE_STORE = 'perception_tree_state';
  var TREE_KEY = 'tree_singleton';
  var treeDirty = false;
  var lastTreeSavedSig = '';

  function openTreeDb() {
    if (!global.indexedDB || !global.IndexedDBModule) {
      return Promise.reject(new Error('IndexedDB не поддерживается или модуль не загружен.'));
    }
    if (treeDb) return Promise.resolve(treeDb);
    return global.IndexedDBModule.openDatabase({
      dbName: TREE_DB_NAME,
      version: TREE_DB_VERSION,
      stores: [{ name: TREE_STORE, options: { keyPath: 'id' } }]
    }).then(function (db) {
      treeDb = db;
      return db;
    });
  }

  function saveTreeToIndexedDB(state) {
    if (!global.IndexedDBModule) return;
    state = state || serializeState();
    function doPut(db) {
      global.IndexedDBModule.put({
        db: db,
        storeName: TREE_STORE,
        value: { id: TREE_KEY, state: state, ts: Date.now() }
      }).catch(function () {});
    }
    if (treeDb) {
      doPut(treeDb);
      return;
    }
    openTreeDb().then(doPut).catch(function () {});
  }

  function scheduleTreeSave() {
    treeDirty = true;
  }

  function loadTreeFromIndexedDB() {
    if (!global.IndexedDBModule) return;
    openTreeDb()
      .then(function (db) {
        return global.IndexedDBModule.get({
          db: db,
          storeName: TREE_STORE,
          key: TREE_KEY
        });
      })
      .then(function (record) {
        if (record && record.state) {
          applyState(record.state);
          var s = serializeState();
          lastTreeSavedSig = (s.compact || '') + '|' + (s.nextId || 0);
        }
      })
      .catch(function () {});
  }

  // ---------- Экспорт ----------

  global.PerceptionTree_addNode = addNode;
  global.PerceptionTree_findActiveBranch = findActiveBranch;
  global.PerceptionTree_findActiveBranchByArray = findActiveBranchByArray;
  global.PerceptionTree_getPathByNodeId = getPathByNodeId;
  global.PerceptionTree_getNode = getNode;
  global.PerceptionTree_getChildren = getChildren;
  global.PerceptionTree_serializeCompact = serializeCompact;
  global.PerceptionTree_deserializeCompact = deserializeCompact;
  global.PerceptionTree_serializeState = serializeState;
  global.PerceptionTree_applyState = applyState;
  global.PerceptionTree_getOrCreateTerminalNode = getOrCreateTerminalNode;
  global.PerceptionTree_getActiveTerminalNodeId = getActiveTerminalNodeId;
  global.PerceptionTree_PATH_LEVEL_NAMES = LEVEL_NAMES;
  global.PerceptionTree_ROOT_PARENT = ROOT_PARENT;

  /**
   * Расшифровка BaseID для show_alert.
   */
  function getBaseIDLabel(baseID) {
    if (baseID === 1) return 'Плохо';
    if (baseID === 2) return 'Норма';
    if (baseID === 3) return 'Хорошо';
    return '—';
  }

  /**
   * Расшифровка EmotionID: список имён активных базовых контекстов.
   */
  function getEmotionIDLabel(emotionId) {
    if (!emotionId || !Array.isArray(global.Emotions) || !Array.isArray(global.BasicContexts)) return '—';
    var ctxIds = null;
    for (var i = 0; i < global.Emotions.length; i++) {
      if (global.Emotions[i] && global.Emotions[i].id === emotionId) {
        ctxIds = global.Emotions[i].contextIds || [];
        break;
      }
    }
    if (!ctxIds || !ctxIds.length) return '—';
    var names = [];
    for (var j = 0; j < ctxIds.length; j++) {
      for (var k = 0; k < global.BasicContexts.length; k++) {
        if (global.BasicContexts[k] && global.BasicContexts[k].id === ctxIds[j]) {
          names.push(global.BasicContexts[k].name || '');
          break;
        }
      }
    }
    return names.filter(Boolean).length ? names.join(', ') : '—';
  }

  /**
   * Список имён активных стимулов (для расшифровки по клику на ActivityID).
   * ActivityID в дереве — это ID образа (FinalImages), при расшифровке показываем имена текущих активных стимулов.
   * Имена берутся только из того массива, в котором стимул активен (один Id может встречаться в разных типах).
   */
  function getActiveStimulusNames() {
    var names = [];
    function addNamesFrom(activeIds, stimulArray) {
      if (!Array.isArray(activeIds) || !Array.isArray(stimulArray)) return;
      for (var i = 0; i < activeIds.length; i++) {
        var id = activeIds[i];
        if (typeof id !== 'number') continue;
        for (var j = 0; j < stimulArray.length; j++) {
          if (stimulArray[j] && stimulArray[j].Id === id) {
            names.push(stimulArray[j].name || ('Id ' + id));
            break;
          }
        }
      }
    }
    addNamesFrom(global.ActiveMotivationStimuls, global.StimulMotivarion);
    addNamesFrom(global.ActivePositivStimuls, global.StimulPositiv);
    addNamesFrom(global.ActiveNegativeStimuls, global.StimulNegative);
    addNamesFrom(global.ActiveNeutralStimuls, global.StimulNeutral);
    return names.length ? names.join(', ') : '—';
  }

  /**
   * Текст детализации автоматизма по ID (для show_alert по щелчку).
   */
  function getAutomatizmDetailText(automatizmId) {
    if (!automatizmId || !Array.isArray(global.Automatizms)) return '—';
    var a = null;
    for (var i = 0; i < global.Automatizms.length; i++) {
      if (global.Automatizms[i] && global.Automatizms[i].id === automatizmId) {
        a = global.Automatizms[i];
        break;
      }
    }
    if (!a) return '—';
    var actionNames = getActionNamesForActionImage(a.actionsImageId);
    var lines = [
      'ID: ' + a.id,
      'Ветка (BranchID): ' + a.branchId,
      'Образ действия (ОД ' + a.actionsImageId + '): ' + actionNames,
      'Полезность (Usefulness): ' + (typeof a.usefulness === 'number' ? a.usefulness.toFixed(3) : '—'),
      'Надёжность (Count): ' + (a.count != null ? a.count : 0),
      'Штатный: ' + (a.isDefault ? 'да' : 'нет')
    ];
    return lines.join('<br>');
  }

  /**
   * Имена действий по ID образа действия (ОД).
   */
  function getActionNamesForActionImage(odId) {
    if (!odId || typeof global.ActionImages_getActionImage !== 'function') return '—';
    var od = global.ActionImages_getActionImage(odId);
    if (!od || !Array.isArray(od.actionIds) || !od.actionIds.length) return '—';
    var names = [];
    var actions = global.BasicActions;
    if (!actions || !Array.isArray(actions)) return od.actionIds.join(', ');
    for (var i = 0; i < od.actionIds.length; i++) {
      var aid = od.actionIds[i];
      var found = false;
      for (var j = 0; j < actions.length; j++) {
        if (actions[j] && actions[j].Id === aid) {
          names.push(actions[j].name || ('Id ' + aid));
          found = true;
          break;
        }
      }
      if (!found) names.push('Id ' + aid);
    }
    return names.length ? names.join(', ') : '—';
  }

  /**
   * Расшифровка ID образа (FinalImage id): по image id находим образ, по его stimulIds — имена.
   * stimulIds хранятся как глобальные id (type*1000+localId), расшифровка через StimulusGlobalId_getName устраняет коллизии.
   */
  function getStimulusNamesForFinalImageId(finalImageId) {
    if (!finalImageId || !Array.isArray(global.FinalImages)) return '—';
    var stimulIds = null;
    for (var i = 0; i < global.FinalImages.length; i++) {
      if (global.FinalImages[i] && global.FinalImages[i].id === finalImageId) {
        stimulIds = global.FinalImages[i].stimulIds;
        break;
      }
    }
    if (!stimulIds || !stimulIds.length) return '—';
    var names = [];
    var getName = typeof global.StimulusGlobalId_getName === 'function' ? global.StimulusGlobalId_getName : null;
    for (var g = 0; g < stimulIds.length; g++) {
      var sid = stimulIds[g];
      var n = getName ? getName(sid) : '';
      if (n) names.push(n); else names.push('Id ' + sid);
    }
    return names.length ? names.join(', ') : '—';
  }

  /**
   * Расшифровка конечного узла ветки: путь по уровням (BaseID, EmotionID, ActivityID и т.д.).
   */
  function getTerminalNodeDecoding(nodeId) {
    var node = nodes.get(nodeId);
    if (!node) return '—';
    var baseLabel = getBaseIDLabel(node.baseID);
    var emotionLabel = getEmotionIDLabel(node.emotionID);
    var activityLabel = '—';
    if (node.activityID && typeof global.FinalImages === 'object') {
      for (var fi = 0; fi < global.FinalImages.length; fi++) {
        if (global.FinalImages[fi] && global.FinalImages[fi].id === node.activityID) {
          var sids = global.FinalImages[fi].stimulIds;
          if (sids && sids.length) {
            var gs = [global.StimulMotivarion, global.StimulPositiv, global.StimulNegative, global.StimulNeutral];
            var nms = [];
            for (var g = 0; g < sids.length; g++) {
              for (var h = 0; h < gs.length; h++) {
                if (!gs[h] || !Array.isArray(gs[h])) continue;
                for (var k = 0; k < gs[h].length; k++) {
                  if (gs[h][k] && gs[h][k].Id === sids[g]) {
                    nms.push(gs[h][k].name || ('Id ' + sids[g]));
                    break;
                  }
                }
              }
            }
            activityLabel = nms.length ? nms.join(', ') : String(node.activityID);
          }
          break;
        }
      }
      if (activityLabel === '—') activityLabel = String(node.activityID);
    } else if (node.activityID) activityLabel = String(node.activityID);
    var parts = [
      'BaseID: ' + baseLabel,
      'EmotionID: ' + emotionLabel,
      'ActivityID: ' + activityLabel,
      'ToneMoodID: ' + (node.toneMoodID || 0),
      'SimbolID: ' + (node.simbolID || 0),
      'PhraseID: ' + (node.phraseID || 0)
    ];
    return parts.join('\n');
  }

  /**
   * Обновить блок индикации активной ветки дерева (первая строка: BaseID -> EmotionID -> ActivityID, ID узла, актуальный стимул; по клику — расшифровка).
   */
  function updateActiveBranchView() {
    var doc = global.document;
    var el = doc && doc.getElementById('perceptionTreeActiveBranchText');
    if (!el) return;

    var progressWrap = doc && doc.getElementById('epWaitingProgressWrap');
    var progressFill = doc && doc.getElementById('epWaitingProgressFill');
    var inWaiting = (global.EpisodicMemory_waitingStartedAt != null) ||
      (typeof global.EpisodicMemory_barShowCompletePulsesLeft === 'number' && global.EpisodicMemory_barShowCompletePulsesLeft > 0);
    if (progressWrap) {
      progressWrap.style.display = inWaiting ? '' : 'none';
    }
    if (progressFill) {
      var period = (typeof global.EpisodicMemory_response_waiting_period === 'number' && global.EpisodicMemory_response_waiting_period > 0)
        ? global.EpisodicMemory_response_waiting_period
        : 15;
      var pct = 0;
      if (typeof global.EpisodicMemory_barShowCompletePulsesLeft === 'number' && global.EpisodicMemory_barShowCompletePulsesLeft > 0) {
        pct = 100;
      } else if (typeof global.EpisodicMemory_pulsesWaiting === 'number' && period > 0) {
        pct = Math.min(100, (global.EpisodicMemory_pulsesWaiting / period) * 100);
      }
      progressFill.style.width = pct.toFixed(1) + '%';
    }

    var baseID = global.BadNormWell || 0;
    var emotionID = 0;
    if (typeof global.Emotions_getOrCreateEmotionId === 'function' && Array.isArray(global.BasicContextsActived)) {
      emotionID = global.Emotions_getOrCreateEmotionId(global.BasicContextsActived) || 0;
    }
    var activityID = 0;
    if (typeof global.FinalImages_getOrCreateFinalImageId === 'function') {
      // Глобальные id стимулов (type*1000+localId), см. stimulus_global_id.js.
      var activeIds = typeof global.StimulusGlobalId_getActiveGlobalIds === 'function'
        ? global.StimulusGlobalId_getActiveGlobalIds()
        : [];
      if (activeIds.length) activityID = global.FinalImages_getOrCreateFinalImageId(activeIds) || 0;
    }
    var terminalNodeId = findActiveBranch(baseID, emotionID, activityID, 0, 0, 0);
    if (terminalNodeId == null) {
      terminalNodeId = 0;
    }
    // Не вызываем getOrCreateTerminalNode здесь — обновление вида не должно создавать узлы и ставить treeDirty каждый пульс.
    // Узлы создаются при активации стимулов (orientation_reflex → getOrCreateTerminalNode).
    var actualId = (typeof global.actual_stimul_ID === 'number' && global.actual_stimul_ID > 0) ? global.actual_stimul_ID : 0;
    var html = 'Активная ветка дерева восприятия (ID=<a href="#" class="pt-branch-link" data-type="node" data-value="' + terminalNodeId + '">' + terminalNodeId + '</a>): BaseID=<a href="#" class="pt-branch-link" data-type="base" data-value="' + baseID + '">' + baseID + '</a> → EmotionID=<a href="#" class="pt-branch-link" data-type="emotion" data-value="' + emotionID + '">' + emotionID + '</a> → ActivityID=<a href="#" class="pt-branch-link" data-type="activity" data-value="' + activityID + '">' + activityID + '</a> Актуальный стимул=<a href="#" class="pt-branch-link" data-type="actual" data-value="' + actualId + '">' + actualId + '</a>';
    var automatismsList = (typeof global.Automatizms_getAutomatizmsByBranchId === 'function')
      ? global.Automatizms_getAutomatizmsByBranchId(terminalNodeId) : [];
    if (automatismsList && automatismsList.length) {
      var aParts = [];
      for (var ai = 0; ai < automatismsList.length; ai++) {
        var aid = automatismsList[ai].id;
        aParts.push('<a href="#" class="pt-branch-link" data-type="automatism" data-value="' + aid + '">' + aid + '</a>');
      }
      html += ' Автоматизмы: ' + aParts.join(', ');
    } else {
      html += ' Автоматизмы: нет';
    }
    el.innerHTML = html;
    var links = el.querySelectorAll('a.pt-branch-link');
    for (var i = 0; i < links.length; i++) {
      (function (link) {
        link.onclick = function (e) {
          e.preventDefault();
          var type = link.getAttribute('data-type');
          var value = link.getAttribute('data-value');
          var text = '—';
          if (type === 'base') text = getBaseIDLabel(parseInt(value, 10));
          else if (type === 'emotion') text = getEmotionIDLabel(parseInt(value, 10));
          else if (type === 'activity') text = getActiveStimulusNames();
          else if (type === 'actual') {
            var actualVal = parseInt(value, 10);
            var curActivityID = 0;
            if (typeof global.FinalImages_getOrCreateFinalImageId === 'function' && typeof global.StimulusGlobalId_getActiveGlobalIds === 'function') {
              var aids = global.StimulusGlobalId_getActiveGlobalIds();
              if (aids.length) curActivityID = global.FinalImages_getOrCreateFinalImageId(aids) || 0;
            }
            if (actualVal === curActivityID) {
              text = getActiveStimulusNames();
            } else {
              text = getStimulusNamesForFinalImageId(actualVal);
            }
          } else if (type === 'node') text = getTerminalNodeDecoding(parseInt(value, 10));
          else if (type === 'automatism') text = getAutomatizmDetailText(parseInt(value, 10));
          if (typeof global.show_alert === 'function') global.show_alert(text);
        };
      })(links[i]);
    }

    var epLine = global.document && global.document.getElementById('perceptionTreeEpisodesLine');
    var btnClearEp = global.document && global.document.getElementById('btnClearEpisodicMemory');
    var totalLabel = global.document && global.document.getElementById('epTotalCountLabel');
    if (epLine && Array.isArray(global.Episodes)) {
      var ep = global.Episodes;
      if (btnClearEp) {
        btnClearEp.style.display = ep.length ? '' : 'none';
        btnClearEp.textContent = '✕';
      }
      if (totalLabel) {
        totalLabel.textContent = ep.length ? ('(всего кадров ЭП: ' + String(ep.length) + ')') : '';
      }
      var last10 = ep.slice(-10).reverse();
      var parts = [];
      for (var ei = 0; ei < last10.length; ei++) {
        var num = ei + 1;
        parts.push('<span class="pt-ep-frame" data-ep-index="' + ei + '" title="Кадр ' + num + ' (щелкните для детализации)">' + num + '</span>');
      }
      epLine.innerHTML = parts.length
        ? ('Последние 10 кадров эпизодической памяти: ' + parts.join(', '))
        : '';
      var frameSpans = epLine.querySelectorAll('.pt-ep-frame');
      for (var si = 0; si < frameSpans.length && si < last10.length; si++) {
        (function (f) {
          frameSpans[si].onclick = function (e) {
            e.preventDefault();
            var node = getNode(f.perceptionNodeId);
            var baseLabel = node ? getBaseIDLabel(node.baseID) : '—';
            var emotionLabel = node ? getEmotionIDLabel(node.emotionID) : '—';
            var stimLabel = getStimulusNamesForFinalImageId(f.stimulusImageId);
            var actLabel = getActionNamesForActionImage(f.actionImageId);
            var lines = ['<b>Условия: </b><br>Базовое состояние: ' + baseLabel + '<br>Эмоция: ' + emotionLabel + '<br>Актуальный стимул: ' + stimLabel + '<br>Ответное действие: ' + actLabel];
            if (f.effect !== null && f.effect !== undefined && !isNaN(f.effect)) {
              var effStr = f.effect >= 0 ? '+' + String(f.effect) : String(f.effect);
              lines.push('Эффект: ' + effStr);
            }
            if (f.responseStimulusImageId != null) {
              var respLabel = getStimulusNamesForFinalImageId(f.responseStimulusImageId);
              lines.push('Ответ оператора: ' + respLabel);
            }
            var msg = lines.join('<br>');
            if (typeof global.show_alert === 'function') global.show_alert(msg);
          };
        })(last10[si]);
      }
    } else {
      if (btnClearEp) {
        btnClearEp.style.display = 'none';
      }
      if (totalLabel) {
        totalLabel.textContent = '';
      }
    }
    if (typeof global.scheduleStimulusReadinessRefreshIfNeeded === 'function') {
      global.scheduleStimulusReadinessRefreshIfNeeded();
    }
  }

  global.PerceptionTree_updateActiveBranchView = updateActiveBranchView;
  global.PerceptionTree_getStimulusNamesForFinalImageId = getStimulusNamesForFinalImageId;
  global.PerceptionTree_getActionNamesForActionImage = getActionNamesForActionImage;

  if (typeof global.registerIndexedDBSaver === 'function') {
    global.registerIndexedDBSaver(function () {
      if (!treeDirty) return;
      treeDirty = false;
      var state = serializeState();
      var sig = (state.compact || '') + '|' + (state.nextId || 0);
      if (sig === lastTreeSavedSig) return;
      lastTreeSavedSig = sig;
      saveTreeToIndexedDB(state);
    });
  }

  loadTreeFromIndexedDB();
  if (global.document && global.document.readyState === 'complete') {
    global.setTimeout(updateActiveBranchView, 0);
  } else if (global.document) {
    global.document.addEventListener('DOMContentLoaded', function () {
      global.setTimeout(updateActiveBranchView, 0);
    });
  }
})(window);
