/**
 * Образы действия (ОД). По аналогии с _3_perception и FinalImage.
 *
 * Массив известных образов действий (ActionImages), сохраняется в IndexedDB и на диске.
 *
 * Структура одного ОД:
 *   - id: number
 *   - finalImageId: number — образ восприятия (0 если не привязан)
 *   - actionIds: number[] — массив ID действий (одно или несколько параллельно)
 *   - odSequence: string — строка ID других ОД через запятую для последовательности (опционально)
 *
 * Формирование: каждый конечный образ восприятия порождает ОД, если хотя бы один
 * из составляющих образ восприятия имеет реакцию. При нескольких реакциях от разных
 * стимулов в ОД прописывается массив этих действий.
 */
(function (global) {
  'use strict';

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

  var nextOdId = 1;

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

  function arraysEqual(a, b) {
    if (a.length !== b.length) return false;
    for (var i = 0; i < a.length; i++) {
      if (a[i] !== b[i]) return false;
    }
    return true;
  }

  function findActionImageByFinalImageId(finalImageId) {
    if (!finalImageId) return null;
    for (var i = 0; i < global.ActionImages.length; i++) {
      if (global.ActionImages[i].finalImageId === finalImageId) {
        return global.ActionImages[i];
      }
    }
    return null;
  }

  function getNextActionImageId() {
    var maxId = 0;
    for (var i = 0; i < global.ActionImages.length; i++) {
      if (global.ActionImages[i].id > maxId) {
        maxId = global.ActionImages[i].id;
      }
    }
    return maxId + 1;
  }

  /**
   * Найти ОД по finalImageId и набору actionIds (и опционально odSequence).
   */
  function findActionImageId(finalImageId, actionIds, odSequence) {
    var normIds = normalizeActionIds(actionIds);
    var seq = (typeof odSequence === 'string') ? odSequence.trim() : '';
    for (var i = 0; i < global.ActionImages.length; i++) {
      var od = global.ActionImages[i];
      if (od.finalImageId !== finalImageId) continue;
      if (!arraysEqual(od.actionIds || [], normIds)) continue;
      if ((od.odSequence || '') !== seq) continue;
      return od.id;
    }
    return 0;
  }

  /**
   * Получить или создать ОД по параметрам.
   * @param {number} finalImageId
   * @param {number[]} actionIds
   * @param {string} [odSequence] — строка ID других ОД через запятую
   * @returns {number} id образа действия
   */
  function getOrCreateActionImageId(finalImageId, actionIds, odSequence) {
    var normIds = normalizeActionIds(actionIds || []);
    var seq = (typeof odSequence === 'string') ? odSequence.trim() : '';
    var existing = findActionImageId(finalImageId, normIds, seq);
    if (existing) return existing;
    var newId = getNextActionImageId();
    global.ActionImages.push({
      id: newId,
      finalImageId: finalImageId || 0,
      actionIds: normIds.slice(0),
      odSequence: seq
    });
    return newId;
  }

  /**
   * Получить или создать ОД для образа восприятия (finalImageId) и добавить в него действие.
   * Вызывается при срабатывании реакции: образ восприятия порождает/дополняет ОД.
   */
  function registerReaction(finalImageId, actionId) {
    if (!finalImageId || !actionId) return 0;
    var od = findActionImageByFinalImageId(finalImageId);
    if (od) {
      var ids = od.actionIds || [];
      if (ids.indexOf(actionId) === -1) {
        ids.push(actionId);
        od.actionIds = normalizeActionIds(ids);
        odDirty = true;
      }
      return od.id;
    }
    var newId = getOrCreateActionImageId(finalImageId, [actionId], '');
    odDirty = true;
    return newId;
  }

  function getActionImage(odId) {
    for (var i = 0; i < global.ActionImages.length; i++) {
      if (global.ActionImages[i].id === odId) return global.ActionImages[i];
    }
    return null;
  }

  // ---------- IndexedDB и сохранение на диск ----------

  var odDb = null;
  var OD_DB_NAME = 'BEAST_ActionImages_DB';
  var OD_DB_VERSION = 1;
  var OD_STORE = 'action_images_state';
  var OD_KEY = 'action_images_singleton';
  var odDirty = false;
  var lastOdSavedSig = '';

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

  function serializeState() {
    var list = [];
    if (Array.isArray(global.ActionImages)) {
      for (var i = 0; i < global.ActionImages.length; i++) {
        var od = global.ActionImages[i];
        if (!od) continue;
        list.push({
          id: od.id,
          finalImageId: od.finalImageId || 0,
          actionIds: Array.isArray(od.actionIds) ? od.actionIds.slice(0) : [],
          odSequence: (typeof od.odSequence === 'string') ? od.odSequence : ''
        });
      }
    }
    return { actionImages: list };
  }

  function applyState(state) {
    if (!state || !Array.isArray(state.actionImages)) return;
    global.ActionImages = [];
    for (var i = 0; i < state.actionImages.length; i++) {
      var o = state.actionImages[i];
      if (!o) continue;
      global.ActionImages.push({
        id: o.id,
        finalImageId: o.finalImageId || 0,
        actionIds: Array.isArray(o.actionIds) ? o.actionIds.slice(0) : [],
        odSequence: (typeof o.odSequence === 'string') ? o.odSequence : ''
      });
    }
    if (typeof global.Goals_ensureDefaultCryGoal === 'function') {
      global.Goals_ensureDefaultCryGoal();
    }
  }

  function saveOdStateToIndexedDB(state) {
    if (!global.IndexedDBModule) return;
    state = state || serializeState();
    function doPut(db) {
      return global.IndexedDBModule.put({
        db: db,
        storeName: OD_STORE,
        value: { id: OD_KEY, state: state, ts: Date.now() }
      }).catch(function () {});
    }
    if (odDb) {
      doPut(odDb);
      return;
    }
    openOdDb().then(doPut).catch(function () {});
  }

  function loadOdStateFromIndexedDB() {
    if (!global.IndexedDBModule) return;
    openOdDb()
      .then(function (db) {
        return global.IndexedDBModule.get({
          db: db,
          storeName: OD_STORE,
          key: OD_KEY
        });
      })
      .then(function (record) {
        if (record && record.state) {
          applyState(record.state);
          lastOdSavedSig = JSON.stringify(serializeState());
        }
      })
      .catch(function () {});
  }

  if (typeof global.registerIndexedDBSaver === 'function') {
    global.registerIndexedDBSaver(function () {
      if (!odDirty) return;
      odDirty = false;
      var state = serializeState();
      var sig = JSON.stringify(state);
      if (sig === lastOdSavedSig) return;
      lastOdSavedSig = sig;
      saveOdStateToIndexedDB(state);
    });
  }

  loadOdStateFromIndexedDB();

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

  global.ActionImages_findActionImageId = findActionImageId;
  global.ActionImages_getOrCreateActionImageId = getOrCreateActionImageId;
  global.ActionImages_registerReaction = registerReaction;
  global.ActionImages_getActionImage = getActionImage;
  global.ActionImages_serializeState = serializeState;
  global.ActionImages_applyState = applyState;

  // Умолчательная цель «Плачь» в goals.js зависит от ОД (BasicActions Id=1); goals.js грузится раньше этого файла.
  if (typeof global.Goals_ensureDefaultCryGoal === 'function') {
    global.Goals_ensureDefaultCryGoal();
  }

})(window);
