/**
 * Сновидения: оркестратор поверх режима сна (sleep_mode.js должен быть активен).
 *
 * Идея одного «сна»:
 * 1) Очередь индексов пустых кадров Episodes (новые → старые), см. episodic_memory.
 * 2) На кадр: старый главный цикл уходит в стек прерываний, создаётся новый главный с thinkingMode=passive
 *    — шаги info_fantasizming даёт диспетчер в том же пульсе после этого onPulse (порядок: puls.js).
 * 3) После MAX_PULSES_PER_FANTASY пульсов в фазе fantasy — реализация: лучший звено по |effect| из fantazmingPlot.chain,
 *    иначе поиск в ЭП; затем AbstractImages_cloneFromEpisodeFrame + markDirty.
 * 4) До MAX_DREAM_FRAMES реконструкций; затем SleepMode_exit(true) — пробуждение с удалением фоновых циклов.
 *
 * SleepDreams_paleActivityBeast: пока идёт фантазия сна, блок «Активность Beast» подсвечивается бледно-серым.
 */
(function (global) {
  'use strict';

  /** Лимит реконструированных за одну сессию сна кадров ЭП; затем принудительный выход из сна с флагом normalDreamCompletion. */
  var MAX_DREAM_FRAMES = 20;
  /** Сколько пульсов отдать пассивному циклу на один пустой кадр до реализации (диспетчер крутит главный шаг после onPulse). */
  var MAX_PULSES_PER_FANTASY = 14;

  /** 'idle' — ждём очередь; 'fantasy' — идёт соновой главный цикл dreamCycleId, считаем fantasyPulseCount. */
  var phase = 'idle';
  var queue = [];
  var dreamCycleId = 0;
  var fantasyPulseCount = 0;
  var currentEpisodeIndex = -1;
  var reconstructed = 0;
  /** Уже обработанные индексы за эту сессию сна (чтобы не зациклить очередь). */
  var processedSet = Object.create(null);

  function clampEffect(x) {
    if (typeof x !== 'number' || isNaN(x)) return 0;
    if (x > 10) return 10;
    if (x < -10) return -10;
    return x;
  }

  /** Перед сменой главного на соновой цикл кладём текущий главный ID в стек прерываний (как при обычном вытеснении). */
  function pushOldMainToBackground() {
    var getMain = global.Consciousness_getMainCycle;
    if (typeof getMain !== 'function') return;
    var old = getMain();
    if (!old || !Array.isArray(global.ConsciousnessBackgroundStack)) return;
    var MAX_STACK = 50;
    if (global.ConsciousnessBackgroundStack.length < MAX_STACK) {
      global.ConsciousnessBackgroundStack.push(old.id);
    }
  }

  function appendDreamLog(cycle, text) {
    if (typeof global.Consciousness_cycleAppendLog !== 'function' || !cycle) return;
    var p = typeof global.cpuls === 'number' ? global.cpuls : 0;
    global.Consciousness_cycleAppendLog(cycle, text, p, { force: true });
  }

  /**
   * Создаёт новый главный цикл без Consciousness_runElementary: стимул из кадра ЭП, пассивный режим, контекст sleepDream.
   * Не вызывается runElementary — только setMain; дальнейшие шаги — обычный dispetchConsciousnessThinking.
   */
  function startDreamCycle(epIdx) {
    currentEpisodeIndex = epIdx;
    var ep = global.Episodes;
    if (!Array.isArray(ep) || epIdx < 0 || epIdx >= ep.length) return false;
    var f = ep[epIdx];
    if (typeof global.EpisodicMemory_isEmptyFrameForSleep === 'function' &&
        !global.EpisodicMemory_isEmptyFrameForSleep(f)) {
      return false;
    }

    var create = global.Consciousness_createCycle;
    var setMain = global.Consciousness_setMainCycle;
    if (typeof create !== 'function' || typeof setMain !== 'function') return false;

    var emotionId = 0;
    if (typeof global.Emotions_getOrCreateEmotionId === 'function' && Array.isArray(global.BasicContextsActived)) {
      emotionId = global.Emotions_getOrCreateEmotionId(global.BasicContextsActived) || 0;
    }

    pushOldMainToBackground();

    var ctx = {
      actual_stimul_ID: f.stimulusImageId || 0,
      branchId: f.perceptionNodeId || 0,
      well: global.BadNormWell || 0,
      emotionId: emotionId,
      activityID: f.stimulusImageId || 0,
      significance: 28,
      sleepDream: true,
      sleepDreamEpisodeIndex: epIdx
    };

    var cycle = create(ctx);
    if (!cycle) return false;

    cycle.thinkingMode = 'passive';
    cycle.context = cycle.context || {};
    cycle.context.sleepDream = true;
    cycle.context.sleepDreamEpisodeIndex = epIdx;
    cycle.fantazmingPlot = { chain: [], lastGeneratedActionId: 0 };
    cycle.fantasizmArr = [];
    cycle.passiveFantasyNoChangeStreak = 0;
    cycle.passiveFantasyLastSig = '';

    setMain(cycle.id);
    dreamCycleId = cycle.id;

    if (global.InfoPicture) {
      global.InfoPicture.thinkingProcessId = cycle.id;
      global.InfoPicture.thinkingImportance = 28;
      if (typeof global.Info_updateView === 'function') global.Info_updateView();
    }

    appendDreamLog(cycle, 'Сон: пассивный цикл по пустому кадру ЭП [индекс ' + String(epIdx) + '], стимул FinalImageId=' + String(f.stimulusImageId) + '.');
    return true;
  }

  /**
   * Снимок цепочки фантазма из главного цикла, дополнение кадра Episodes[currentEpisodeIndex], абстракции, удаление сонового цикла.
   * При ошибочном индексе — только снять dreamCycleId с реестра.
   */
  function finalizeDreamFrame() {
    var mc = typeof global.Consciousness_getCycle === 'function' ? global.Consciousness_getCycle(dreamCycleId) : null;
    var ep = global.Episodes;
    if (!Array.isArray(ep) || currentEpisodeIndex < 0 || currentEpisodeIndex >= ep.length) {
      if (typeof global.Consciousness_deleteCycle === 'function' && dreamCycleId) {
        global.Consciousness_deleteCycle(dreamCycleId);
      }
      dreamCycleId = 0;
      return;
    }
    var emptyF = ep[currentEpisodeIndex];
    var chain = (mc && mc.fantazmingPlot && Array.isArray(mc.fantazmingPlot.chain)) ? mc.fantazmingPlot.chain : [];

    var best = null;
    var bestAbs = -1;
    for (var i = 0; i < chain.length; i++) {
      var ch = chain[i];
      if (!ch) continue;
      var ab = Math.abs(typeof ch.effect === 'number' ? ch.effect : 0);
      if (ab > bestAbs) {
        bestAbs = ab;
        best = ch;
      }
    }

    // Сначала цепочка фантазма (макс. |effect|); если нет источника в Episodes — fallback по стимулу кадра в ЭП.
    var filledFromChain = false;
    if (emptyF && best && best.episodeIndex != null) {
      var src = ep[best.episodeIndex];
      if (src) {
        if (typeof src.effect === 'number' && !isNaN(src.effect)) {
          emptyF.effect = clampEffect(src.effect);
        } else if (typeof best.effect === 'number') {
          emptyF.effect = clampEffect(best.effect);
        } else {
          emptyF.effect = 0;
        }
        var rs = src.responseStimulusImageId != null ? parseInt(src.responseStimulusImageId, 10) : 0;
        emptyF.responseStimulusImageId = rs > 0 ? rs : (emptyF.stimulusImageId || 0);
        filledFromChain = true;
      }
    }
    if (!filledFromChain && emptyF && typeof global.EpisodicMemory_getFramesByResponseStimulus === 'function' &&
        typeof global.EpisodicMemory_pickBestFrameByAbsEffect === 'function') {
      var cand = global.EpisodicMemory_getFramesByResponseStimulus(emptyF.stimulusImageId || 0);
      var pick = global.EpisodicMemory_pickBestFrameByAbsEffect(cand);
      if (pick) {
        emptyF.effect = clampEffect(typeof pick.effect === 'number' ? pick.effect : 0);
        var rs2 = pick.responseStimulusImageId != null ? parseInt(pick.responseStimulusImageId, 10) : 0;
        emptyF.responseStimulusImageId = rs2 > 0 ? rs2 : (emptyF.stimulusImageId || 0);
      } else if (emptyF) {
        emptyF.effect = 0;
        emptyF.responseStimulusImageId = emptyF.stimulusImageId || 0;
        if (mc) {
          appendDreamLog(mc, 'Сон: реализация — нет кандидатов в ЭП, кадр закрыт нейтрально.');
        }
      }
    }

    if (emptyF && emptyF.effect !== null && emptyF.effect !== undefined) {
      if (typeof global.AbstractImages_cloneFromEpisodeFrame === 'function') {
        try {
          global.AbstractImages_cloneFromEpisodeFrame(emptyF);
        } catch (e) {}
      }
      if (typeof global.EpisodicMemory_markDirty === 'function') {
        global.EpisodicMemory_markDirty();
      }
    }

    if (mc) {
      appendDreamLog(mc, 'Сон: реализация — кадр [' + String(currentEpisodeIndex) + '] дополнен (фантазм → абстракции).');
    }

    if (typeof global.Consciousness_deleteCycle === 'function' && dreamCycleId) {
      global.Consciousness_deleteCycle(dreamCycleId);
    }
    dreamCycleId = 0;
    processedSet[String(currentEpisodeIndex)] = true;
    reconstructed += 1;
  }

  function rebuildQueue() {
    queue = [];
    if (typeof global.EpisodicMemory_collectEmptyFrameIndicesNewestFirst !== 'function') return;
    var all = global.EpisodicMemory_collectEmptyFrameIndicesNewestFirst();
    for (var qi = 0; qi < all.length; qi++) {
      var ix = all[qi];
      if (!processedSet[String(ix)]) queue.push(ix);
    }
  }

  /** Старт сессии сна: сброс машины состояний и новая очередь пустых кадров. */
  function onSleepEntered() {
    phase = 'idle';
    dreamCycleId = 0;
    fantasyPulseCount = 0;
    currentEpisodeIndex = -1;
    reconstructed = 0;
    processedSet = Object.create(null);
    rebuildQueue();
    if (typeof global.console !== 'undefined' && global.console.log) {
      global.console.log('SleepDreamProcess: очередь пустых кадров', queue.length);
    }
  }

  /** Пробуждение (любое): убрать соновой цикл, очистить очередь, снять бледную активность. */
  function onSleepExited() {
    phase = 'idle';
    if (typeof global.Consciousness_deleteCycle === 'function' && dreamCycleId) {
      try {
        global.Consciousness_deleteCycle(dreamCycleId);
      } catch (e) {}
    }
    dreamCycleId = 0;
    fantasyPulseCount = 0;
    currentEpisodeIndex = -1;
    queue = [];
    processedSet = Object.create(null);
    global.SleepDreams_paleActivityBeast = false;
  }

  /**
   * Вызывается из puls.js ДО dispetchConsciousnessThinking: в фазе fantasy в этом же пульсе диспетчер даёт шаг
   * соновому главному циклу (info_fantasizming). Счётчик fantasyPulseCount увеличивается после этого шага.
   */
  function onPulse() {
    if (!global.SleepMode_isActive || !global.SleepMode_isActive()) {
      global.SleepDreams_paleActivityBeast = false;
      return;
    }

    global.SleepDreams_paleActivityBeast = (phase === 'fantasy' && dreamCycleId > 0);

    if (reconstructed >= MAX_DREAM_FRAMES) {
      if (typeof global.SleepMode_exit === 'function') {
        global.SleepMode_exit(true);
      }
      return;
    }

    if (phase === 'idle') {
      if (!queue.length) {
        rebuildQueue();
      }
      if (!queue.length) {
        return;
      }
      var epIdx = queue.shift();
      if (!startDreamCycle(epIdx)) {
        phase = 'idle';
        return;
      }
      phase = 'fantasy';
      fantasyPulseCount = 0;
      return;
    }

    if (phase === 'fantasy') {
      var main = typeof global.Consciousness_getMainCycle === 'function' ? global.Consciousness_getMainCycle() : null;
      if (!main || main.id !== dreamCycleId) {
        phase = 'idle';
        dreamCycleId = 0;
        return;
      }
      fantasyPulseCount += 1;
      if (fantasyPulseCount >= MAX_PULSES_PER_FANTASY) {
        finalizeDreamFrame();
        phase = 'idle';
        if (reconstructed >= MAX_DREAM_FRAMES) {
          if (typeof global.SleepMode_exit === 'function') {
            global.SleepMode_exit(true);
          }
        }
      }
    }
  }

  global.SleepDreamProcess_MAX_FRAMES = MAX_DREAM_FRAMES;
  global.SleepDreamProcess_onSleepEntered = onSleepEntered;
  global.SleepDreamProcess_onSleepExited = onSleepExited;
  global.SleepDreamProcess_onPulse = onPulse;
})(window);
