/**
 * Условные рефлексы (синонимы стимулов) для Beast.
 *
 * Базовая идея взята из OLD_CODE/InsectAgent/sys/condition_reflex.js:
 * - сочетание стимулов (условных) несколько раз сочетается с раздражителем,
 *   запускающим врождённый (генетический) рефлекс;
 * - в конкретном сочетании базовых контекстов (контекст = образ ситуации);
 * - после нескольких подтверждений (tryCount > 3) одно и то же сочетание стимулов
 *   в том же контексте запускает то же действие.
 *
 * Дополнение по сравнению с прототипом:
 * - если одновременно активировано несколько стимулов (любых видов), из них
 *   строится совокупный конечный образ (FinalImageId в FinalImage.js);
 * - именно этот FinalImageId становится "ключом" условного рефлекса;
 * - условный рефлекс создаёт синоним реагирования не только для нейтральных,
 *   а для любых видов стимулов;
 * - при формировании и срабатывании условного рефлекса соответствующий
 *   совокупный стимул (FinalImageId) должен быть активен "здесь и сейчас",
 *   иначе условный рефлекс не формируется и не запускается.
 *
 * Действие — это Id из window.BasicActions, запускаемый через
 * runGeneticActionFromSynonymReflex(), чтобы сохранить общую механику Beast.
 */
(function (global) {
  'use strict';

  /**
   * Структура условного (синонимического) рефлекса:
   * {
   *   id: number,
   *   context: string,      // "0,3,9" — сочетание базовых контекстов
   *   finalImageId: number, // Id конечного образа (FinalImageId)
   *   actionId: number,     // Id действия из BasicActions
   *   birthTime: number,    // Cur_puls_val при создании
   *   lastActivation: number, // последний пуск (Cur_puls_val)
   *   tryCount: number      // число подтверждённых сочетаний (зрелость > 3)
   * }
   */

  if (!Array.isArray(global.SynonymReflexes)) {
    global.SynonymReflexes = [];
  }
  if (typeof global.NextSynonymReflexId !== 'number') {
    global.NextSynonymReflexId = 0;
  }

  var REFLEX_READY_THRESHOLD = 3; // порог зрелости условного рефлекса (tryCount > threshold)
  var DEBUG_SYNONYM_REFLEX = false; // тестовый alert отключён по умолчанию
  var PrevActiveStimulIdsKey = null; // ключ активных стимулов для подавления повторного запуска на том же наборе

  /** Сравнение двух массивов как множеств (одни и те же элементы, порядок не важен). */
  function setsEqual(a, b) {
    if (!Array.isArray(a) || !Array.isArray(b)) return false;
    var sa = a.slice(0).sort(function (x, y) { return x - y; });
    var sb = b.slice(0).sort(function (x, y) { return x - y; });
    if (sa.length !== sb.length) return false;
    for (var i = 0; i < sa.length; i++) {
      if (sa[i] !== sb[i]) return false;
    }
    return true;
  }

  /** Устойчивый строковый ключ набора id стимулов (для анти-спама). */
  function activeIdsKey(ids) {
    if (!Array.isArray(ids) || !ids.length) return '';
    var a = ids.slice(0).sort(function (x, y) { return x - y; });
    return a.join(',');
  }

  function getCurrentPulse() {
    var v = global.Cur_puls_val;
    if (typeof v === 'number' && !isNaN(v)) return v;
    return 0;
  }

  /**
   * Построить строковый ключ текущего контекста для условных рефлексов.
   *
   * КОНТЕКСТ = сочетание:
   * - базового состояния BadNormWell (1 — Плохо, 2 — Норма, 3 — Хорошо);
   * - набора активных базовых контекстов (их id из BasicContextsActived).
   *
   * Формат ключа: "BadNormWell:id1,id2,..." например "1:10,13" или "3:9".
   * Такой ключ устойчив между сессиями (не зависит от внутренних EmotionId).
   * Если BadNormWell ещё не определён — возвращается 'none'.
   */
  function getCurrentContextKey() {
    var well = global.BadNormWell || 0;
    if (!well) return 'none';

    var ctxIds = [];
    if (Array.isArray(global.BasicContextsActived)) {
      for (var i = 0; i < global.BasicContextsActived.length; i++) {
        var cid = global.BasicContextsActived[i];
        if (typeof cid === 'number') {
          ctxIds.push(cid);
        }
      }
    }
    ctxIds.sort(function (a, b) { return a - b; });
    var ctxPart = ctxIds.length ? ctxIds.join(',') : '0';

    return String(well) + ':' + ctxPart;
  }

  function findSynonymReflexIndex(contextKey, finalImageId, actionId) {
    for (var i = 0; i < global.SynonymReflexes.length; i++) {
      var cr = global.SynonymReflexes[i];
      if (!cr) continue;
      if (cr.context === contextKey &&
          cr.finalImageId === finalImageId &&
          cr.actionId === actionId) {
        return i;
      }
    }
    return -1;
  }

  function getActionName(actionId) {
    var name = '';
    if (Array.isArray(global.BasicActions)) {
      for (var i = 0; i < global.BasicActions.length; i++) {
        var a = global.BasicActions[i];
        if (a && a.Id === actionId) {
          name = a.name || '';
          break;
        }
      }
    }
    return name;
  }

  function showTransientAlert(message) {
    if (typeof global.show_alert !== 'function') return;
    try {
      global.show_alert(message);
      if (typeof global.close_alert === 'function') {
        global.setTimeout(function () {
          try {
            global.close_alert();
          } catch (e) {}
        }, 1500);
      }
    } catch (e2) {}
  }

  function debugAlert(message) {
    if (!DEBUG_SYNONYM_REFLEX) return;
  }

  // ---------- Сохранение условных рефлексов в IndexedDB ----------

  var urDb = null;
  var UR_DB_NAME = 'BEAST_UR_DB';
  var UR_DB_VERSION = 1;
  var UR_STORE = 'conditional_reflexes_state';
  var UR_KEY = 'ur_singleton';
  var urDirty = false;
  var lastURSavedSig = '';

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

  function serializeConditionalReflexesState() {
    var list = [];
    if (Array.isArray(global.SynonymReflexes)) {
      for (var i = 0; i < global.SynonymReflexes.length; i++) {
        var r = global.SynonymReflexes[i];
        if (!r) continue;
        list.push({
          id: r.id,
          context: r.context,
          finalImageId: r.finalImageId,
          stimulIds: Array.isArray(r.stimulIds) ? r.stimulIds.slice(0) : [],
          actionId: r.actionId,
          birthTime: r.birthTime,
          lastActivation: r.lastActivation,
          tryCount: r.tryCount,
          isReady: !!r.isReady
        });
      }
    }
    return {
      reflexes: list,
      nextId: (typeof global.NextSynonymReflexId === 'number' ? global.NextSynonymReflexId : 0)
    };
  }

  function applyConditionalReflexesState(state) {
    if (!state) return;
    var list = Array.isArray(state.reflexes) ? state.reflexes : [];
    global.SynonymReflexes = [];
    for (var i = 0; i < list.length; i++) {
      var r = list[i];
      if (!r) continue;
      var tryCount = typeof r.tryCount === 'number' ? r.tryCount : 0;
      // После загрузки все зрелые рефлексы считаем готовыми к запуску,
      // чтобы не требовался лишний «шаг обучения» после обновления страницы.
      var isReady = tryCount > REFLEX_READY_THRESHOLD ? true : !!r.isReady;
      global.SynonymReflexes.push({
        id: r.id,
        context: r.context,
        finalImageId: r.finalImageId,
        stimulIds: Array.isArray(r.stimulIds) ? r.stimulIds.slice(0) : [],
        actionId: r.actionId,
        birthTime: r.birthTime,
        lastActivation: r.lastActivation,
        tryCount: tryCount,
        isReady: isReady
      });
    }
    if (typeof state.nextId === 'number' && state.nextId >= 0) {
      global.NextSynonymReflexId = state.nextId;
    } else {
      var maxId = 0;
      for (var j = 0; j < global.SynonymReflexes.length; j++) {
        if (global.SynonymReflexes[j].id > maxId) {
          maxId = global.SynonymReflexes[j].id;
        }
      }
      global.NextSynonymReflexId = maxId;
    }
  }

  function saveURStateToIndexedDB(state) {
    if (!global.IndexedDBModule) return;
    state = state || serializeConditionalReflexesState();
    function doPut(db) {
      return global.IndexedDBModule.put({
        db: db,
        storeName: UR_STORE,
        value: {
          id: UR_KEY,
          state: state,
          ts: Date.now()
        }
      }).catch(function () {});
    }
    if (urDb) {
      doPut(urDb);
      return;
    }
    openURDb().then(doPut).catch(function () {});
  }

  function scheduleURStateSave() {
    urDirty = true;
  }

  function loadURStateFromIndexedDB() {
    if (!global.IndexedDBModule) return;
    openURDb()
      .then(function (db) {
        return global.IndexedDBModule.get({
          db: db,
          storeName: UR_STORE,
          key: UR_KEY
        });
      })
      .then(function (record) {
        if (record && record.state) {
          applyConditionalReflexesState(record.state);
          lastURSavedSig = JSON.stringify(serializeConditionalReflexesState());
        }
      })
      .catch(function () {});
  }

  /**
   * Сбросить все условные рефлексы в памяти и удалить их из IndexedDB.
   * Вызывается по кнопке «Ух» в UI.
   */
  function clearAllConditionalReflexes() {
    global.SynonymReflexes = [];
    global.NextSynonymReflexId = 0;
    PrevActiveStimulIdsKey = null;
    var emptyState = { reflexes: [], nextId: 0 };
    lastURSavedSig = JSON.stringify(emptyState);
    if (global.IndexedDBModule) {
      function doPut(db) {
        global.IndexedDBModule.put({
          db: db,
          storeName: UR_STORE,
          value: {
            id: UR_KEY,
            state: emptyState,
            ts: Date.now()
          }
        }).catch(function () {});
      }
      if (urDb) {
        doPut(urDb);
      } else {
        openURDb().then(doPut).catch(function () {});
      }
    }
  }

  // Заглушка для совместимости с UI нейтральных стимулов.
  // В текущей версии условный рефлекс опирается на совокупный FinalImageId,
  // поэтому отдельное запоминание "предыдущего нейтрального" не требуется.
  function onNeutralStimulusActivated(stimId) {
    void stimId;
  }

  /**
   * Попытка формирования/усиления условного рефлекса.
   * Вызывается из genetic_reflexes_engine, когда сработал генетический рефлекс
   * (из StimulMotivarion / StimulPositiv / StimulNegative).
   *
   * stimId     — Id стимула-«безусловного раздражителя»
   * sourceType — 'motivation' | 'positive' | 'negative'
   * actionId   — Id действия из BasicActions, которое реально выполнено.
   */
  function onGeneticActionFired(stimId, sourceType, actionId) {
    if (typeof actionId !== 'number' || actionId < 0) return;
    if (typeof global.FinalImages_getOrCreateFinalImageId !== 'function') {
      return;
    }

    // Глобальные id стимулов (type*1000+localId) — устраняют коллизии между массивами (см. stimulus_global_id.js).
    var activeIds = typeof global.StimulusGlobalId_getActiveGlobalIds === 'function'
      ? global.StimulusGlobalId_getActiveGlobalIds()
      : [];

    // Если нет ни одного активного стимула — условный рефлекс не формируется.
    if (!activeIds.length) {
      return;
    }

    // Глобальный id стимула, вызвавшего реакцию (для исключения его из образа условного рефлекса).
    var triggerGlobalId = 0;
    if (typeof global.StimulusGlobalId_toGlobal === 'function' && typeof global.StimulusGlobalId_getTypeFromSourceType === 'function') {
      var type = global.StimulusGlobalId_getTypeFromSourceType(sourceType);
      triggerGlobalId = global.StimulusGlobalId_toGlobal(type, stimId);
    }

    // Важно: не создавать условный рефлекс по самому стимулу, который уже запустил реакцию.
    if (activeIds.length === 1 && activeIds[0] === triggerGlobalId) {
      return;
    }

    // Совокупный образ для условного рефлекса — все активные стимулы КРОМЕ триггера (глобальные id).
    var conditionalIds = [];
    for (var ci = 0; ci < activeIds.length; ci++) {
      if (activeIds[ci] !== triggerGlobalId) {
        conditionalIds.push(activeIds[ci]);
      }
    }
    if (!conditionalIds.length) {
      return;
    }

    var contextNow = getCurrentContextKey();
    if (!contextNow || contextNow === 'none') {
      return;
    }

    // Текущий совокупный образ предшествующих стимулов (FinalImageId) — ключ условного рефлекса.
    var finalImageId = global.FinalImages_getOrCreateFinalImageId(conditionalIds);
    if (!finalImageId && finalImageId !== 0) {
      return;
    }

    var now = getCurrentPulse();

    var idx = findSynonymReflexIndex(contextNow, finalImageId, actionId);
    if (idx === -1) {
      // Создаём прототип условного рефлекса
      global.NextSynonymReflexId += 1;
      var newReflex = {
        id: global.NextSynonymReflexId,
        context: contextNow,
        finalImageId: finalImageId,
        stimulIds: conditionalIds.slice(0), // предшествующие стимулы
        actionId: actionId,
        birthTime: now,
        lastActivation: now,
        tryCount: 1,
        isReady: false
      };
      global.SynonymReflexes.push(newReflex);

      // 1-е сочетание стимула и реакции
      var aName1 = getActionName(actionId) || ('действие ' + actionId);
      showTransientAlert('Зафиксировано 1-е сочетание стимула и реакции («' + aName1 + '»).');

      if (typeof global.setBeastActivityText === 'function') {
        try {
          global.setBeastActivityText('Создан прототип условного рефлекса (FinalImageId ' +
            finalImageId + ' → действие ' + actionId + ')');
        } catch (e) {}
      }
      scheduleURStateSave();
    } else {
      // Усиливаем уже существующий прототип
      var cr = global.SynonymReflexes[idx];
      var oldCount = cr.tryCount;

      // После достижения порога зрелости больше не спамим сообщениями
      // и не наращиваем tryCount, только обновляем время последней активации.
      if (oldCount > REFLEX_READY_THRESHOLD) {
        cr.lastActivation = now;
      } else {
        cr.tryCount += 1;
        cr.lastActivation = now;

        // Сообщение об N-м сочетании (пока рефлекс ещё укрепляется)
        var aName2 = getActionName(actionId) || ('действие ' + actionId);
        showTransientAlert('Зафиксировано ' + cr.tryCount + '-е сочетание стимула и реакции («' + aName2 + '»).');

        // Переход через порог зрелости: образован новый условный рефлекс.
        // Но он станет "готовым" только после гашения участвовавших стимулов.
        if (oldCount <= REFLEX_READY_THRESHOLD && cr.tryCount > REFLEX_READY_THRESHOLD) {
          showTransientAlert('Образован новый условный рефлекс с реакцией «' + aName2 + '».');
          cr.isReady = false; // станет true после полного гашения стимула(ов)
        }
      }

      if (typeof global.setBeastActivityText === 'function') {
        try {
          global.setBeastActivityText('Усилен (' + cr.tryCount +
            ') прототип условного рефлекса (FinalImageId ' +
            finalImageId + ' → действие ' + actionId + ')');
        } catch (e2) {}
      }
      scheduleURStateSave();
    }
  }

  /**
   * Попытка запустить уже сформированные условные рефлексы.
   * Вызывается из processGeneticReflexes() ДО обработки генетических карт.
   *
   * Возвращает true, если какой-то условный рефлекс был запущен.
   */
  function tryRunSynonymReflexes() {
    if (typeof global.FinalImages_getOrCreateFinalImageId !== 'function') {
      return false;
    }

    // Контекст учитывается: условный рефлекс должен запускаться
    // только в том же сочетании (BadNormWell, EmotionId), в котором обучался.
    var contextNow = getCurrentContextKey();
    if (!contextNow || contextNow === 'none') {
      return false;
    }

    // Совокупный активный образ — глобальные id (type*1000+localId), см. stimulus_global_id.js.
    var activeIds = typeof global.StimulusGlobalId_getActiveGlobalIds === 'function'
      ? global.StimulusGlobalId_getActiveGlobalIds()
      : [];

    if (!activeIds.length) {
      // Все стимулы погашены — сбрасываем ключ, чтобы при следующей активации UR мог сработать
      PrevActiveStimulIdsKey = null;
      // Также обновим флаг готовности для всех рефлексов: если их образ сейчас не активен, они могут стать ready
      // (гашение образа завершено).
      var iReset;
      for (iReset = 0; iReset < global.SynonymReflexes.length; iReset++) {
        var r = global.SynonymReflexes[iReset];
        if (!r) continue;
        if (r.tryCount > REFLEX_READY_THRESHOLD) {
          r.isReady = true;
        }
      }
      // Сохраним обновлённое состояние готовности в IndexedDB
      scheduleURStateSave();
      return false;
    }

    // Сравниваем по набору стимулов, а не по FinalImageId — после перезагрузки
    // тот же набор может получить другой id, если FinalImages загрузился позже или иначе.
    var currentKey = activeIdsKey(activeIds);
    if (!currentKey) return false;

    if (PrevActiveStimulIdsKey !== null && PrevActiveStimulIdsKey === currentKey) {
      return false;
    }

    var now = getCurrentPulse();

    // Ищем зрелый и готовый рефлекс по совпадению набора активных стимулов с stimulIds рефлекса.
    // Сначала — строго в текущем контексте, затем (если не нашли) допускаем запуск
    // в «близком» контексте: по тем же стимулам, но с другим contextKey.
    var candidateAnyCtx = null;
    for (var j = 0; j < global.SynonymReflexes.length; j++) {
      var cr = global.SynonymReflexes[j];
      if (!cr) continue;
      if (!setsEqual(activeIds, cr.stimulIds)) continue;
      if (cr.context !== contextNow) continue;
      if (cr.tryCount <= REFLEX_READY_THRESHOLD) continue;
      if (!cr.isReady) continue;
      candidateAnyCtx = cr;
      break;
    }

    // Если строго в том же контексте не нашли — ищем любой зрелый UR
    // по тем же стимулам, но игнорируя context. Это позволяет рефлексу
    // срабатывать после перезагрузки даже если строковый ключ контекста
    // немного изменился (например, порядок/состав базовых контекстов).
    if (!candidateAnyCtx) {
      for (var k = 0; k < global.SynonymReflexes.length; k++) {
        var cr2 = global.SynonymReflexes[k];
        if (!cr2) continue;
        if (!setsEqual(activeIds, cr2.stimulIds)) continue;
        if (cr2.tryCount <= REFLEX_READY_THRESHOLD) continue;
        if (!cr2.isReady) continue;
        candidateAnyCtx = cr2;
        break;
      }
    }

    var chosen = candidateAnyCtx;
    if (chosen) {
      var finalImageId = global.FinalImages_getOrCreateFinalImageId
        ? global.FinalImages_getOrCreateFinalImageId(activeIds) : 0;
      debugAlert('RUN: сработал UR, actionId=' + chosen.actionId +
        ' FI=' + finalImageId);
      if (typeof global.runGeneticActionFromSynonymReflex === 'function') {
        global.runGeneticActionFromSynonymReflex(chosen.actionId, 'synonym', finalImageId);
      }
      chosen.lastActivation = now;
      chosen.tryCount += 1;
      PrevActiveStimulIdsKey = currentKey;
      // фиксируем изменения UR (счётчик, lastActivation) в IndexedDB
      scheduleURStateSave();
      return true;
    }

    return false;
  }

  // Экспорт API для связи с UI и генетическими рефлексами
  global.synonyms_onNeutralStimulusActivated = onNeutralStimulusActivated;
  global.synonyms_onGeneticActionFired = onGeneticActionFired;
  global.synonyms_tryRunReflexes = tryRunSynonymReflexes;

  // Вспомогательная функция может быть полезна при отладке
  global.get_cur_context_for_synonyms = getCurrentContextKey;
  global.serializeConditionalReflexesState = serializeConditionalReflexesState;
  global.applyConditionalReflexesState = applyConditionalReflexesState;
  global.clearAllConditionalReflexes = clearAllConditionalReflexes;

  // Загрузка ранее сохранённых условных рефлексов при инициализации модуля
  loadURStateFromIndexedDB();

  // Кнопка «Ух» — сброс всех условных рефлексов
  (function bindResetButton() {
    try {
      var btn = global.document && global.document.getElementById('btnResetConditionalReflexes');
      if (btn && typeof btn.addEventListener === 'function') {
        btn.addEventListener('click', function () {
          if (!global.confirm('Уверены, что нужно удалить все условные рефлексы?')) return;
          clearAllConditionalReflexes();
        });
      }
    } catch (e) {}
  })();

  if (typeof global.registerIndexedDBSaver === 'function') {
    global.registerIndexedDBSaver(function () {
      if (!urDirty) return;
      urDirty = false;
      var state = serializeConditionalReflexesState();
      var sig = JSON.stringify(state);
      if (sig === lastURSavedSig) return;
      lastURSavedSig = sig;
      saveURStateToIndexedDB(state);
    });
  }

})(window);

