/**
 * Ментальные автоматизмы и абстракции (заготовка).
 

Ментальные автоматизмы
Теория.
Как дети учатся сначала считать в столбик (моторные автоматизмы в виде ментальной последовательности), а потом оперировать в уме с образами и умножать в уме.
В школе сначала учат предметному мышлению: как использовать предметы, чтобы получать желаемый результат.
Как использовать молоток - это быстро и наглядно, дети легко отзеркаливают.
Как считать с помощью костяшей счетов - это сложнее и требует заучивания последовательности действий, 
Как умножать и делить в столбик - тоже с помощью моторных действий получают ментальный результат.
Потом эта последовательность становится ментальным автоматизмом и выполняется быстро.
И, наконенец, учат, как не прибегая к посредничеству моторных действий в уме оперировать образами, так же как предметами, и получать результат. Это уже чистый ментальный автоматизм.
Если работа с запоминанием моторных действий сначала фиксируется в виде цепочек в эпизодической памяти в виде Правил (стимул-действие-эффект), то потом появляются ментальные правила оперирования образами в виде образа цепочки последовательных мыслительных действий оперирования с образами.
Примеры простейших оперирований с образами с получаемым эффектом.
Детализация образа
Представьте яблоко целиком, затем мысленно "увеличьте" его поверхность: видите красную кожуру с мелкими пятнышками, чёренок с листочком. Эффект — образ становится ярче и детальнее, как будто вы его изучили вблизи, что улучшает запоминание.
Вращение образа
Возьмите образ кубика: мысленно поверните его на 90 градусов по часовой стрелке. Теперь видна другая грань с иной картинкой. Эффект — доступны скрытые детали (например, все 6 граней), что помогает анализировать объект со всех сторон без реального поворота.
Видоизменение образа
Представьте "машину": сначала как маленькую игрушку, затем как грузовик, потом как гоночный болид. Эффект — один стимул порождает серию вариаций, развивая гибкость ассоциаций и понимание категории.
Сложение образов
Сложите мысленно образ дома и дерева: дом стоит слева, дерево справа, ветки касаются крыши. Эффект — новый композиционный образ, который можно "прочитать" как сцену или историю.
Разделение образа
Разбейте образ круга на 4 сектора, мысленно окрасьте каждый в разный цвет (красный, синий, зелёный, жёлтый). Эффект — круг превращается в диаграмму, показывающую пропорции или категории без рисования.
Образ: В уме удерживаются два множества точек (например, как на гранях кубика: 3 точки и 2 точки).
Ментальное правило (бывшее моторное): «Сдвинь множества в одну кучу». Раньше ребенок сдвигал фишки руками. Теперь он мысленно «накладывает» одну группу на другую или объединяет их в одном пространстве.
Эффект: Мгновенное возникновение образа «5» без вербального счета «три... четыре... пять».
Почему это автоматизм: Мозг не считает, он распознает новую конфигурацию как целостный гештальт, сформированный предыдущим опытом физических перемещений.
Образ: В уме держится буква «R» или простой геометрический контур.
Ментальное правило: «Поверни образ на 90 градусов по часовой стрелке». Это калька с физического поворота детали в руках.
Эффект: Получение нового образа (перевернутая «R») и сравнение его с эталоном (например, «подходит ли эта деталь в паз?»).
Связь с памятью: Сначала ребенок крутил деталь руками (эпизодическая память: «я крутил, и она встала»). Теперь он крутит образ детали, и правило «поворот меняет ориентацию» работает автоматически.
Образ: Два отрезка разной длины, предъявленные последовательно (первый исчез, появился второй).
Ментальное правило: «Скопируй первый отрезок и мысленно положи его поверх второго». Это имитация действия с линейкой или двумя палочками.
Эффект: Визуальное выявление разницы (один «выступает» за край другого).
Автоматизм: Не нужно измерять в сантиметрах. Операция «наложения» становится инструментом мышления, как молоток.
Образ: Визуализированные ряды костяшек (например, 5 в нижнем ряду, 2 в верхнем).
Ментальное правило: «Сдвинь одну костяшку вниз» (операция +1).
Эффект: Изменение визуальной конфигурации, которое считывается как новое число.
Суть: Руки не двигаются, но активируется та же нейронная сеть, что и при реальном движении. Правило «стимул (задача) — действие (сдвиг костяшки) — эффект (новое число)» полностью перенесено в ментальный план.
Эпизодическое правило: «Я беру молоток (стимул), бью (действие), гвоздь входит (эффект)». Запомнено как конкретный случай.
Ментальное правило: «Я беру образ молотка, совершаю имитацию удара в воображении, предвижу вхождение гвоздя».
Чистый ментальный автоматизм: При виде задачи «забить гвоздь» мозг мгновенно активирует схему «удар -> результат», минуя сознательное проговаривание действий. Образ действия сливается с образом цели.
На высшем уровне абстракции образ сам становится инструментом.
Реализация.

 * Требование: массивы должны иметь долговременную систему сохранения:
 * - IndexedDB (инкрементно по dirty)
 * - сохранение в файл по кнопке через BEAST.persistentModules (serialize/apply)
 *
 * Здесь пока нет логики ментальных действий; только структуры и сохранение.
 */
(function (global) {
  'use strict';

  // Абстракции верхнего уровня (будущая база для «символов» и композиций).
  if (!Array.isArray(global.Abstractions)) global.Abstractions = [];
  // Ментальные образы действий (аналог ActionImages, но для мышления).
  if (!Array.isArray(global.MentalActionImages)) global.MentalActionImages = [];
  // Ментальные правила (аналог «Правил» ЭП, но без ответного стимула): Stimulus → Abstraction → Effect.
  if (!Array.isArray(global.MentalEpisodeRules)) global.MentalEpisodeRules = [];
  // Ментальные автоматизмы (аналог моторных, но запускают MentalActionImages).
  if (!Array.isArray(global.MentalAutomatizms)) global.MentalAutomatizms = [];

  var nextMentalAutomatizmId = 1;
  var nextMentalActionImageId = 1;
  var nextMentalRuleId = 1;
  var nextAbstractionId = 1;

  var memDirty = false;
  var lastSavedSig = '';

  function markDirty() {
    memDirty = true;
  }

  function normalizeIds(arr) {
    if (!Array.isArray(arr)) return [];
    var out = [];
    for (var i = 0; i < arr.length; i++) {
      var v = parseInt(arr[i], 10);
      if (!isNaN(v) && v > 0 && out.indexOf(v) === -1) out.push(v);
    }
    out.sort(function (a, b) { return a - b; });
    return out;
  }

  function serializeState() {
    return {
      nextMentalAutomatizmId: nextMentalAutomatizmId,
      nextMentalActionImageId: nextMentalActionImageId,
      nextMentalRuleId: nextMentalRuleId,
      nextAbstractionId: nextAbstractionId,
      mentalAutomatizms: global.MentalAutomatizms.slice(0),
      mentalActionImages: global.MentalActionImages.slice(0),
      mentalEpisodeRules: global.MentalEpisodeRules.slice(0),
      abstractions: global.Abstractions.slice(0)
    };
  }

  function applyState(state) {
    state = state || {};
    nextMentalAutomatizmId = parseInt(state.nextMentalAutomatizmId, 10);
    if (isNaN(nextMentalAutomatizmId) || nextMentalAutomatizmId < 1) nextMentalAutomatizmId = 1;
    nextMentalActionImageId = parseInt(state.nextMentalActionImageId, 10);
    if (isNaN(nextMentalActionImageId) || nextMentalActionImageId < 1) nextMentalActionImageId = 1;
    nextMentalRuleId = parseInt(state.nextMentalRuleId, 10);
    if (isNaN(nextMentalRuleId) || nextMentalRuleId < 1) nextMentalRuleId = 1;
    nextAbstractionId = parseInt(state.nextAbstractionId, 10);
    if (isNaN(nextAbstractionId) || nextAbstractionId < 1) nextAbstractionId = 1;

    global.MentalAutomatizms = Array.isArray(state.mentalAutomatizms) ? state.mentalAutomatizms.slice(0) : [];
    global.MentalActionImages = Array.isArray(state.mentalActionImages) ? state.mentalActionImages.slice(0) : [];
    global.MentalEpisodeRules = Array.isArray(state.mentalEpisodeRules) ? state.mentalEpisodeRules.slice(0) : [];
    global.Abstractions = Array.isArray(state.abstractions) ? state.abstractions.slice(0) : [];

    // защита от конфликтов ID
    for (var i = 0; i < global.MentalAutomatizms.length; i++) {
      var a = global.MentalAutomatizms[i];
      if (a && typeof a.id === 'number' && a.id >= nextMentalAutomatizmId) nextMentalAutomatizmId = a.id + 1;
    }
    for (var mi = 0; mi < global.MentalActionImages.length; mi++) {
      var m = global.MentalActionImages[mi];
      if (m && typeof m.id === 'number' && m.id >= nextMentalActionImageId) nextMentalActionImageId = m.id + 1;
    }
    for (var ri = 0; ri < global.MentalEpisodeRules.length; ri++) {
      var r = global.MentalEpisodeRules[ri];
      if (r && typeof r.id === 'number' && r.id >= nextMentalRuleId) nextMentalRuleId = r.id + 1;
    }
    for (var j = 0; j < global.Abstractions.length; j++) {
      var b = global.Abstractions[j];
      if (b && typeof b.id === 'number' && b.id >= nextAbstractionId) nextAbstractionId = b.id + 1;
    }

    memDirty = false;
    try {
      lastSavedSig = JSON.stringify(serializeState());
    } catch (e) {
      lastSavedSig = '';
    }
  }

  // -------------------- Ментальные образы действий --------------------
  // { id, sourceActionImageId, abstractActionIds:number[], maSequence:string }
  function MentalActionImages_getOrCreate(sourceActionImageId, abstractActionIds, maSequence) {
    var src = parseInt(sourceActionImageId, 10) || 0;
    var ids = normalizeIds(abstractActionIds);
    var seq = typeof maSequence === 'string' ? maSequence.trim() : '';
    for (var i = 0; i < global.MentalActionImages.length; i++) {
      var m = global.MentalActionImages[i];
      if (!m) continue;
      if ((m.sourceActionImageId || 0) !== src) continue;
      var a1 = Array.isArray(m.abstractActionIds) ? normalizeIds(m.abstractActionIds) : [];
      if (a1.length !== ids.length) continue;
      var ok = true;
      for (var j = 0; j < ids.length; j++) {
        if (a1[j] !== ids[j]) { ok = false; break; }
      }
      if (!ok) continue;
      if ((m.maSequence || '') !== seq) continue;
      return m.id;
    }
    var id = nextMentalActionImageId++;
    global.MentalActionImages.push({
      id: id,
      sourceActionImageId: src,
      abstractActionIds: ids.slice(0),
      maSequence: seq
    });
    markDirty();
    return id;
  }

  function MentalActionImages_get(id) {
    id = parseInt(id, 10);
    if (isNaN(id) || id <= 0) return null;
    for (var i = 0; i < global.MentalActionImages.length; i++) {
      var m = global.MentalActionImages[i];
      if (m && m.id === id) return m;
    }
    return null;
  }

  // -------------------- Ментальные правила (эпизоды) --------------------
  // { id, perceptionNodeId, stimulusImageId, abstractionId, meanEffect, count, lastUsedAt, createdAt }
  function MentalRules_addSample(perceptionNodeId, stimulusImageId, abstractionId, effect) {
    if (!perceptionNodeId || !stimulusImageId || !abstractionId) return 0;
    if (typeof effect !== 'number' || isNaN(effect)) return 0;
    var key = String(perceptionNodeId) + '|' + String(stimulusImageId) + '|' + String(abstractionId);
    var existing = null;
    for (var i = 0; i < global.MentalEpisodeRules.length; i++) {
      var r = global.MentalEpisodeRules[i];
      if (!r) continue;
      if (String(r.perceptionNodeId) + '|' + String(r.stimulusImageId) + '|' + String(r.abstractionId) === key) {
        existing = r;
        break;
      }
    }
    if (!existing) {
      existing = {
        id: nextMentalRuleId++,
        perceptionNodeId: perceptionNodeId,
        stimulusImageId: stimulusImageId,
        abstractionId: abstractionId,
        meanEffect: 0,
        count: 0,
        createdAt: Date.now(),
        lastUsedAt: Date.now()
      };
      global.MentalEpisodeRules.push(existing);
    }
    var c = typeof existing.count === 'number' ? existing.count : 0;
    var cur = typeof existing.meanEffect === 'number' ? existing.meanEffect : 0;
    existing.meanEffect = (cur * c + effect) / (c + 1);
    existing.count = c + 1;
    existing.lastUsedAt = Date.now();
    markDirty();
    return existing.id;
  }

  // abstractClone = { abstractPerceptionId, abstractActionId } из abstract_images после closeLastFrame ЭП.
  // При «нулевом» ОД в кадре abstractActionId = 0 — берётся abstractPerceptionId (см. about_episodic_memory.md).
  function MentalRules_registerFromEpisodeFrame(frame, abstractClone) {
    if (!frame || !abstractClone) return 0;
    var pn = frame.perceptionNodeId;
    var stim = frame.stimulusImageId;
    var effect = frame.effect;
    var absId = abstractClone.abstractActionId || abstractClone.abstractPerceptionId || 0;
    if (!absId) return 0;
    return MentalRules_addSample(pn, stim, absId, effect);
  }

  // -------------------- Ментальные автоматизмы --------------------
  // { id, themeId, situationId, branchId, usefulness, mentalActionImageId, count, isDefault }
  function MentalAutomatizms_create(payload) {
    if (!payload || typeof payload !== 'object') payload = {};
    var obj = Object.assign({}, payload);
    obj.id = nextMentalAutomatizmId++;
    if (typeof obj.usefulness !== 'number') obj.usefulness = 0;
    if (typeof obj.count !== 'number') obj.count = 0;
    obj.isDefault = !!obj.isDefault;
    global.MentalAutomatizms.push(obj);
    markDirty();
    return obj.id;
  }

  function MentalAutomatizms_getDefaultForThemeSituation(themeId, situationId) {
    themeId = parseInt(themeId, 10) || 0;
    situationId = parseInt(situationId, 10) || 0;
    for (var i = 0; i < global.MentalAutomatizms.length; i++) {
      var a = global.MentalAutomatizms[i];
      if (!a) continue;
      if (a.isDefault && (a.themeId || 0) === themeId && (a.situationId || 0) === situationId) return a;
    }
    return null;
  }

  function MentalAutomatizms_setDefault(id) {
    id = parseInt(id, 10);
    if (isNaN(id) || id <= 0) return;
    var target = null;
    for (var i = 0; i < global.MentalAutomatizms.length; i++) {
      if (global.MentalAutomatizms[i] && global.MentalAutomatizms[i].id === id) {
        target = global.MentalAutomatizms[i];
        break;
      }
    }
    if (!target) return;
    var themeId = target.themeId || 0;
    var situationId = target.situationId || 0;
    for (var j = 0; j < global.MentalAutomatizms.length; j++) {
      var a = global.MentalAutomatizms[j];
      if (!a) continue;
      if ((a.themeId || 0) === themeId && (a.situationId || 0) === situationId) a.isDefault = false;
    }
    target.isDefault = true;
    markDirty();
  }

  function Abstractions_create(payload) {
    if (!payload || typeof payload !== 'object') payload = {};
    var obj = Object.assign({}, payload);
    obj.id = nextAbstractionId++;
    global.Abstractions.push(obj);
    markDirty();
    return obj.id;
  }

  // Вывод на пульт при активации ментального образа действия (аналог моторного, но без эффекторов).
  function setBeastActivityText(text) {
    if (!global.document) return;
    var block = global.document.getElementById('beastActivityBlock');
    if (!block) return;
    block.textContent = text || '';
    // Дублируем визуал сна с genetic_reflexes_engine: ментальные автоматизмы во сне — бледный цвет.
    if (text && global.SleepDreams_paleActivityBeast === true) {
      block.style.color = '#b0b0b0';
      block.style.fontWeight = 'normal';
      block.style.fontSize = '12pt';
    }
  }

  function runMentalActionImage(mentalActionImageId) {
    var m = MentalActionImages_get(mentalActionImageId);
    if (!m) return;
    var ids = Array.isArray(m.abstractActionIds) ? m.abstractActionIds : [];
    var line1 = 'Ментальная операция (ментальный автоматизм)';
    var line2 = 'Абстрактные действия: ' + (ids.length ? ids.join(', ') : '—');
    setBeastActivityText(line1 + '\n' + line2);
  }

  function MentalAutomatizms_run(id) {
    id = parseInt(id, 10);
    if (isNaN(id) || id <= 0) return false;
    var target = null;
    for (var i = 0; i < global.MentalAutomatizms.length; i++) {
      var a = global.MentalAutomatizms[i];
      if (a && a.id === id) { target = a; break; }
    }
    if (!target || !target.mentalActionImageId) return false;
    runMentalActionImage(target.mentalActionImageId);
    return true;
  }

  function findAbstractPerceptionRecordId(perceptionNodeId, stimulusImageId) {
    perceptionNodeId = parseInt(perceptionNodeId, 10) || 0;
    stimulusImageId = parseInt(stimulusImageId, 10) || 0;
    if (!perceptionNodeId || !stimulusImageId) return 0;
    var arr = global.AbstractPerceptionImages;
    if (!Array.isArray(arr)) return 0;
    for (var pi = 0; pi < arr.length; pi++) {
      var o = arr[pi];
      if (!o) continue;
      if ((o.perceptionNodeId || 0) !== perceptionNodeId) continue;
      if ((o.stimulusImageId || 0) !== stimulusImageId) continue;
      return typeof o.id === 'number' ? o.id : 0;
    }
    return 0;
  }

  function findAbstractActionRecordId(perceptionNodeId, actionImageId) {
    perceptionNodeId = parseInt(perceptionNodeId, 10) || 0;
    actionImageId = parseInt(actionImageId, 10) || 0;
    if (!perceptionNodeId || !actionImageId) return 0;
    var arr = global.AbstractActionImages;
    if (!Array.isArray(arr)) return 0;
    for (var ai = 0; ai < arr.length; ai++) {
      var o = arr[ai];
      if (!o) continue;
      if ((o.perceptionNodeId || 0) !== perceptionNodeId) continue;
      if ((o.actionImageId || 0) !== actionImageId) continue;
      return typeof o.id === 'number' ? o.id : 0;
    }
    return 0;
  }

  function findMentalAutomatizmByThemeSituationMai(themeId, situationId, mentalActionImageId) {
    themeId = parseInt(themeId, 10) || 0;
    situationId = parseInt(situationId, 10) || 0;
    mentalActionImageId = parseInt(mentalActionImageId, 10) || 0;
    for (var fi = 0; fi < global.MentalAutomatizms.length; fi++) {
      var a = global.MentalAutomatizms[fi];
      if (!a) continue;
      if ((a.themeId || 0) !== themeId) continue;
      if ((a.situationId || 0) !== situationId) continue;
      if ((a.mentalActionImageId || 0) !== mentalActionImageId) continue;
      return a;
    }
    return null;
  }

  /**
   * После ответа оператора: при позитивном effect и наличии кадра semanticStructure (abstract_images) —
   * ментальный образ действия + ментальный автоматизм по умолчанию для пары themeId=ветка, situationId.
   *
   * @param {Object} params — perceptionNodeId, situationId, actualStimulusImageId, episodeEffect
   * @returns {boolean}
   */
  function MentalAutomatizms_tryCreateFromSemanticStructureRule(params) {
    params = params || {};
    var branchId = parseInt(params.perceptionNodeId, 10) || 0;
    var situationId = parseInt(params.situationId, 10) || 0;
    var actualStim = parseInt(params.actualStimulusImageId, 10) || 0;
    var episodeEffect = params.episodeEffect;
    if (typeof episodeEffect !== 'number' || isNaN(episodeEffect) || episodeEffect <= 0) return false;
    if (!branchId || !actualStim) return false;
    if (typeof global.AbstractImages_findRuleFrameMatchingContext !== 'function') return false;

    var fr = global.AbstractImages_findRuleFrameMatchingContext({
      perceptionNodeId: branchId,
      situationId: situationId,
      actualStimulusImageId: actualStim
    });
    if (!fr || fr.actionImageId == null) return false;
    var odId = parseInt(fr.actionImageId, 10) || 0;
    if (!odId) return false;

    var aaRecId = findAbstractActionRecordId(branchId, odId);
    var apRecId = findAbstractPerceptionRecordId(branchId, actualStim);
    var absIds = [];
    if (aaRecId) absIds.push(aaRecId);
    else if (apRecId) absIds.push(apRecId);
    if (!absIds.length) return false;

    var maiId = MentalActionImages_getOrCreate(odId, absIds, '');
    if (!maiId) return false;

    var existing = findMentalAutomatizmByThemeSituationMai(branchId, situationId, maiId);
    var mid;
    if (existing) {
      var u = typeof existing.usefulness === 'number' ? existing.usefulness : 0;
      var c = typeof existing.count === 'number' ? existing.count : 0;
      existing.usefulness = (u * c + episodeEffect) / (c + 1);
      existing.count = c + 1;
      mid = existing.id;
      markDirty();
    } else {
      mid = MentalAutomatizms_create({
        themeId: branchId,
        situationId: situationId,
        branchId: branchId,
        mentalActionImageId: maiId,
        usefulness: episodeEffect,
        count: 1,
        isDefault: false
      });
    }
    MentalAutomatizms_setDefault(mid);
    return true;
  }

  // --- IndexedDB ---
  var DB_NAME = 'BEAST_MentalAutomatizms_DB';
  var DB_VERSION = 1;
  var STORE = 'mental_automatizms_state';
  var KEY = 'mental_automatizms_singleton';

  function openDb() {
    if (!global.IndexedDBModule || typeof global.IndexedDBModule.openDB !== 'function') {
      return Promise.reject(new Error('IndexedDBModule not available'));
    }
    return global.IndexedDBModule.openDB({
      dbName: DB_NAME,
      version: DB_VERSION,
      storeName: STORE,
      keyPath: 'id'
    });
  }

  function loadFromIndexedDB() {
    openDb()
      .then(function (d) {
        return global.IndexedDBModule.get({ db: d, storeName: STORE, key: KEY });
      })
      .then(function (record) {
        if (record && record.state) {
          applyState(record.state);
          if (typeof global.Info_updateView === 'function') global.Info_updateView();
        }
      })
      .catch(function () {});
  }

  function clearAllMentalAutomatizms() {
    global.MentalAutomatizms = [];
    nextMentalAutomatizmId = 1;
    markDirty();
    if (typeof global.Info_updateView === 'function') global.Info_updateView();
  }

  function clearAllAbstractions() {
    global.Abstractions = [];
    nextAbstractionId = 1;
    markDirty();
    if (typeof global.Info_updateView === 'function') global.Info_updateView();
  }

  loadFromIndexedDB();

  if (typeof global.registerIndexedDBSaver === 'function') {
    global.registerIndexedDBSaver(function () {
      if (!memDirty) return;
      memDirty = false;
      var state = serializeState();
      var sig = '';
      try { sig = JSON.stringify(state); } catch (e) { sig = ''; }
      if (sig && sig === lastSavedSig) return;
      lastSavedSig = sig;
      openDb()
        .then(function (db) {
          return global.IndexedDBModule.put({
            db: db,
            storeName: STORE,
            value: { id: KEY, state: state, ts: Date.now() }
          });
        })
        .catch(function () {});
    });
  }

  // --- Экспорт ---
  global.MentalActionImages_getOrCreate = MentalActionImages_getOrCreate;
  global.MentalActionImages_get = MentalActionImages_get;
  global.MentalRules_registerFromEpisodeFrame = MentalRules_registerFromEpisodeFrame;
  global.MentalAutomatizms_create = MentalAutomatizms_create;
  global.MentalAutomatizms_setDefault = MentalAutomatizms_setDefault;
  global.MentalAutomatizms_getDefaultForThemeSituation = MentalAutomatizms_getDefaultForThemeSituation;
  global.MentalAutomatizms_run = MentalAutomatizms_run;
  global.MentalAutomatizms_tryCreateFromSemanticStructureRule = MentalAutomatizms_tryCreateFromSemanticStructureRule;
  global.Abstractions_create = Abstractions_create;
  global.MentalAutomatizms_serializeState = serializeState;
  global.MentalAutomatizms_applyState = applyState;
  global.MentalAutomatizms_clearAll = clearAllMentalAutomatizms;
  global.Abstractions_clearAll = clearAllAbstractions;
})(window);



