/**
 * Автоматизмы (_11_Automatizms).
 *
 * Автоматизм привязан к узлу ветки дерева восприятия (BranchID) и к образу действия (ActionsImageID).
 * Активируется или блокируется в процессе осознания (stimuls_consciousness) при активации узла.
 *
 * Структура: id, branchId, usefulness, actionsImageId, count, isDefault.
 * - Usefulness: < 0 вред, > 0 польза (уточняется в период ожидания после активности).
 * - Count: число запусков для усреднения Usefulness.
 * - isDefault: штатный автоматизм для ветки (только один на ветку).
 *
 * Уникальность по паре (branchId, actionsImageId). Сохранение в IndexedDB и в файл.
 *
 * ПРЕЕМСТВЕННОСТЬ ОЦЕНКИ ЭФФЕКТА (ЭП → автоматизм):
 * Значение effect для addUsefulnessSample приходит из ЭП (closeLastFrame: effect = diffAfter - diffBefore).
 * Одна и та же оценка эффекта записывается в кадр ЭП и передаётся в штатный автоматизм ветки —
 * без дублирования и расхождений; Usefulness усредняется по тем же величинам, что и кадры эпизодов.
 */
(function (global) {
  'use strict';

  if (!Array.isArray(global.Automatizms)) {
    global.Automatizms = [];
  }

  var nextId = 1;
  /** Индекс по branchId → массив автоматизмов этой ветки (для быстрого поиска и штатного). */
  var byBranchId = Object.create(null);
  var automatismsDirty = false;
  var lastAutomatizmsSavedSig = '';

  function rebuildIndex() {
    byBranchId = Object.create(null);
    var arr = global.Automatizms;
    if (!Array.isArray(arr)) return;
    for (var i = 0; i < arr.length; i++) {
      var a = arr[i];
      if (!a || a.branchId == null) continue;
      var key = String(a.branchId);
      if (!byBranchId[key]) byBranchId[key] = [];
      byBranchId[key].push(a);
    }
  }

  function getNextId() {
    var max = 0;
    var arr = global.Automatizms;
    for (var i = 0; i < (arr && arr.length) || 0; i++) {
      if (arr[i] && arr[i].id > max) max = arr[i].id;
    }
    return max + 1;
  }

  /**
   * Все автоматизмы, привязанные к ветке branchId.
   * @param {number} branchId — ID узла дерева восприятия
   * @returns {Object[]}
   */
  function getAutomatizmsByBranchId(branchId) {
    if (branchId == null || branchId === 0) return [];
    var list = byBranchId[String(branchId)];
    return Array.isArray(list) ? list.slice(0) : [];
  }

  /**
   * Штатный автоматизм для ветки (isDefault === true). Не более одного на ветку.
   * @param {number} branchId
   * @returns {Object|null}
   */
  function getDefaultAutomatizmForBranch(branchId) {
    var list = getAutomatizmsByBranchId(branchId);
    for (var i = 0; i < list.length; i++) {
      if (list[i].isDefault) return list[i];
    }
    return null;
  }

  /**
   * Найти автоматизм по паре (branchId, actionsImageId).
   * @returns {Object|null}
   */
  function findAutomatizmByBranchAndAction(branchId, actionsImageId) {
    var list = byBranchId[String(branchId)];
    if (!Array.isArray(list)) return null;
    for (var i = 0; i < list.length; i++) {
      if (list[i].actionsImageId === actionsImageId) return list[i];
    }
    return null;
  }

  /**
   * Создать автоматизм. Не создаёт дубль по (branchId, actionsImageId) — возвращает id существующего.
   * Требуется реальный actionsImageId (существующий в ActionImages).
   * @param {number} branchId — ID узла дерева восприятия
   * @param {number} actionsImageId — ID образа действия (ОД)
   * @returns {number} id созданного или существующего автоматизма, 0 если actionsImageId недопустим
   */
  function createAutomatizm(branchId, actionsImageId) {
    if (branchId == null || branchId === 0) return 0;
    var odId = parseInt(actionsImageId, 10);
    if (isNaN(odId) || odId <= 0) return 0;
    if (typeof global.ActionImages_getActionImage === 'function' && !global.ActionImages_getActionImage(odId)) {
      return 0;
    }
    var existing = findAutomatizmByBranchAndAction(branchId, odId);
    if (existing) return existing.id;

    var id = getNextId();
    var obj = {
      id: id,
      branchId: branchId,
      usefulness: 0,
      actionsImageId: odId,
      count: 0,
      isDefault: false
    };
    global.Automatizms.push(obj);
    var key = String(branchId);
    if (!byBranchId[key]) byBranchId[key] = [];
    byBranchId[key].push(obj);
    automatismsDirty = true;
    return id;
  }

  /**
   * Назначить автоматизм штатным для своей ветки. Остальные автоматизмы этой ветки получают isDefault=false.
   * @param {number} automatizmId
   */
  function setDefaultAutomatizm(automatizmId) {
    var id = parseInt(automatizmId, 10);
    if (isNaN(id) || id <= 0) return;
    var arr = global.Automatizms;
    var target = null;
    for (var i = 0; i < (arr && arr.length) || 0; i++) {
      if (arr[i] && arr[i].id === id) {
        target = arr[i];
        break;
      }
    }
    if (!target) return;
    var branchId = target.branchId;
    var list = byBranchId[String(branchId)];
    if (Array.isArray(list)) {
      for (var j = 0; j < list.length; j++) {
        list[j].isDefault = false;
      }
    }
    target.isDefault = true;
    automatismsDirty = true;
  }

  /**
   * Обновить usefulness и count после периода ожидания (по эффекту из ЭП).
   * ПРЕЕМСТВЕННОСТЬ: effect — тот же, что записан в кадре ЭП (closeLastFrame), единая оценка.
   * @param {number} automatizmId
   * @param {number} effect — значение эффекта из ЭП (diffAfter - diffBefore) для усреднения usefulness
   */
  function addUsefulnessSample(automatizmId, effect) {
    var id = parseInt(automatizmId, 10);
    if (isNaN(id) || id <= 0) return;
    var arr = global.Automatizms;
    for (var i = 0; i < (arr && arr.length) || 0; i++) {
      if (arr[i] && arr[i].id === id) {
        var a = arr[i];
        var n = (a.count || 0) + 1;
        var u = a.usefulness != null ? a.usefulness : 0;
        a.usefulness = (u * (n - 1) + (typeof effect === 'number' ? effect : 0)) / n;
        a.count = n;
        automatismsDirty = true;
        return;
      }
    }
  }

  function serializeState() {
    var arr = global.Automatizms;
    if (!Array.isArray(arr)) return { automatizms: [], nextId: 1 };
    var list = [];
    for (var i = 0; i < arr.length; i++) {
      var a = arr[i];
      if (!a) continue;
      list.push({
        id: a.id,
        branchId: a.branchId,
        usefulness: a.usefulness != null ? a.usefulness : 0,
        actionsImageId: a.actionsImageId,
        count: a.count != null ? a.count : 0,
        isDefault: !!a.isDefault
      });
    }
    return { automatizms: list, nextId: getNextId() };
  }

  function applyState(state) {
    if (!state || !Array.isArray(state.automatizms)) {
      global.Automatizms = [];
      rebuildIndex();
      return;
    }
    global.Automatizms = [];
    var list = state.automatizms;
    for (var i = 0; i < list.length; i++) {
      var s = list[i];
      if (!s || s.id == null) continue;
      global.Automatizms.push({
        id: s.id,
        branchId: s.branchId != null ? s.branchId : 0,
        usefulness: typeof s.usefulness === 'number' ? s.usefulness : 0,
        actionsImageId: s.actionsImageId != null ? s.actionsImageId : 0,
        count: (s.count != null && s.count >= 0) ? s.count : 0,
        isDefault: !!s.isDefault
      });
    }
    if (typeof state.nextId === 'number' && state.nextId > 0) {
      nextId = state.nextId;
    } else {
      nextId = getNextId();
    }
    rebuildIndex();
    lastAutomatizmsSavedSig = JSON.stringify(serializeState());
    automatismsDirty = false;
  }

  var db = null;
  var DB_NAME = 'BEAST_Automatizms_DB';
  var DB_VERSION = 1;
  var STORE = 'automatizms_state';
  var KEY = 'automatizms_singleton';

  function openDb() {
    if (!global.indexedDB || !global.IndexedDBModule) return Promise.reject(new Error('IndexedDB не доступен'));
    if (db) return Promise.resolve(db);
    return global.IndexedDBModule.openDatabase({
      dbName: DB_NAME,
      version: DB_VERSION,
      stores: [{ name: STORE, options: { keyPath: 'id' } }]
    }).then(function (d) {
      db = d;
      return d;
    });
  }

  function saveToIndexedDB(state) {
    if (!global.IndexedDBModule) return;
    state = state || serializeState();
    function put(db) {
      return global.IndexedDBModule.put({
        db: db,
        storeName: STORE,
        value: { id: KEY, state: state, ts: Date.now() }
      }).catch(function () {});
    }
    if (db) {
      put(db);
      return;
    }
    openDb().then(put).catch(function () {});
  }

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

  if (typeof global.registerIndexedDBSaver === 'function') {
    global.registerIndexedDBSaver(function () {
      if (!automatismsDirty) return;
      automatismsDirty = false;
      var state = serializeState();
      var sig = JSON.stringify(state);
      if (sig === lastAutomatizmsSavedSig) return;
      lastAutomatizmsSavedSig = sig;
      saveToIndexedDB(state);
    });
  }

  function clearAllAutomatizms() {
    global.Automatizms = [];
    rebuildIndex();
    nextId = 1;
    automatismsDirty = true;
    if (typeof global.Info_updateView === 'function') global.Info_updateView();
  }

  rebuildIndex();
  loadFromIndexedDB();

  global.Automatizms_getAutomatizmsByBranchId = getAutomatizmsByBranchId;
  global.Automatizms_getDefaultAutomatizmForBranch = getDefaultAutomatizmForBranch;
  global.Automatizms_createAutomatizm = createAutomatizm;
  global.Automatizms_setDefaultAutomatizm = setDefaultAutomatizm;
  global.Automatizms_addUsefulnessSample = addUsefulnessSample;
  global.Automatizms_serializeState = serializeState;
  global.Automatizms_applyState = applyState;
  global.Automatizms_clearAll = clearAllAutomatizms;
})(window);
