// Game Store — coordinator, tick pipeline, time/incursion
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { HOURS_PER_TICK, MAX_DAY, EQUIPMENT_TYPES } from '../constants';
import { computeEquipmentEffects } from '../effects';
import type { ComputedEffects } from '../effects/upgrade-effects.types';

import { computeDisciplineEffects } from '../effects/discipline-effects';
import { computeMaxMana, computeRegen, getMeditationBonus, getIncursionStrength, calcInsight, getActiveEquipmentSpells } from '../utils';
import { getElementDistance } from '../utils/element-distance';
import { computeConversionRates } from '../utils/conversion-rates';
import { mergePerElementCapBonuses } from '../utils/element-cap-bonus';
import { processPactRitual } from './pipelines/pact-ritual';
import { buildCombatCallbacks } from './pipelines/combat-tick';
import { useUIStore } from './uiStore';
import { usePrestigeStore } from './prestigeStore';
import { useManaStore } from './manaStore';
import { useCombatStore } from './combatStore';
import { useAttunementStore } from './attunementStore';
import { useCraftingStore } from './craftingStore';
import { useDisciplineStore } from './discipline-slice';
import { createResetGame, createGatherMana } from './gameActions';
import { createSafeStorage } from '../utils/safe-persist';
import { createStartNewLoop } from './gameLoopActions';
import { buildTickContext, applyTickWrites } from './tick-pipeline';
import { processEnchantingTicks } from './pipelines/enchanting-tick';
import { buildGolemCombatPipeline } from './pipelines/golem-combat';
import { getGuardianForFloor } from '../data/guardian-encounters';
import { buildConversionParams } from '../utils/conversion-params';

import type { TickContext, TickWrites } from './tick-pipeline';
import type { GameCoordinatorState } from './gameStore.types';
import type { EnemyState } from '../types';
import { applyEnemyDefenses as applyEnemyDefensesFromPipeline } from './pipelines/combat-tick';


// Track paused conversions already logged to avoid flooding the activity log every tick
const loggedPausedConversions = new Set<string>();

export interface GameCoordinatorStore extends GameCoordinatorState {
  tick: () => void;
  resetGame: () => void;
  togglePause: () => void;
  startNewLoop: () => void;
  initGame: () => void;
  gatherMana: () => void;
}

const initialState: GameCoordinatorState = {
  day: 1,
  hour: 0,
  incursionStrength: 0,
  containmentWards: 0,
  initialized: false,
};

export const useGameStore = create<GameCoordinatorStore>()(
  persist(
    (set, get) => ({
      ...initialState,

      initGame: () => {
        useDisciplineStore.getState().setPracticingCallbacks({
          onStartPracticing: () => useCombatStore.getState().startPracticing(),
          onStopPracticing: () => useCombatStore.getState().stopPracticing(),
        });
        set({ initialized: true });
      },

      tick: () => {
        try {
          const ctx = buildTickContext({
            game: get(),
            ui: useUIStore.getState(),
            prestige: usePrestigeStore.getState(),
            mana: useManaStore.getState(),
            combat: useCombatStore.getState(),
            crafting: useCraftingStore.getState(),
            attunement: useAttunementStore.getState(),
            discipline: useDisciplineStore.getState(),
          });

          if (ctx.ui.gameOver || ctx.ui.paused) return;

          // eslint-disable-next-line @typescript-eslint/no-explicit-any
          const storeSetters = {
            setGame: set,
            setUI: (w: any) => useUIStore.setState(w),
            setPrestige: (w: any) => usePrestigeStore.setState(w),
            setMana: (w: any) => useManaStore.setState(w),
            setCombat: (w: any) => useCombatStore.setState(w),
            setCrafting: (w: any) => useCraftingStore.setState(w),
            setAttunement: (w: any) => useAttunementStore.setState(w),
            setDiscipline: (w: any) => useDisciplineStore.setState(w),
            addLogs: (msgs: string[]) => msgs.forEach((m) => useUIStore.getState().addLog(m)),
          };

          const writes: TickWrites = { logs: [] };
          const addLog = (msg: string) => writes.logs.push(msg);

          const steadyHandLevel = ctx.prestige.prestigeUpgrades.steadyHand || 0;
          const enchantmentPowerMultiplier = 1 + steadyHandLevel * 0.15;
          const equipmentEffects = computeEquipmentEffects(
            ctx.crafting.equipmentInstances || {},
            ctx.crafting.equippedInstances || {},
            enchantmentPowerMultiplier,
          );
          const disciplineEffects = computeDisciplineEffects();
          const allSpecials = new Set<string>([
            ...equipmentEffects.specials,
            ...disciplineEffects.specials,
          ]);
          const effects = { specials: allSpecials } as ComputedEffects;

          const maxMana = computeMaxMana(
            { prestigeUpgrades: ctx.prestige.prestigeUpgrades },
            undefined,
            disciplineEffects,
          );
          const baseRegen = computeRegen(
            { prestigeUpgrades: ctx.prestige.prestigeUpgrades, attunements: ctx.attunement.attunements },
            undefined,
            disciplineEffects,
          ) * (1 + (disciplineEffects.multipliers.regenMultiplier || 0));

          let hour = ctx.game.hour + HOURS_PER_TICK;
          let day = ctx.game.day;
          if (hour >= 24) {
            hour -= 24;
            day += 1;
          }

          const insightParams = {
            maxFloorReached: ctx.combat.maxFloorReached,
            totalManaGathered: ctx.mana.totalManaGathered,
            signedPacts: ctx.prestige.signedPacts,
            prestigeUpgrades: ctx.prestige.prestigeUpgrades,
          };

          if (ctx.combat.maxFloorReached >= 100 && ctx.prestige.signedPacts.includes(100)) {
            const insightGained = calcInsight(insightParams, disciplineEffects) * 3;
            addLog('VICTORY! The Awakened One falls! Gained ' + insightGained + ' Insight!');
            writes.ui = { ...(writes.ui || {}), gameOver: true, victory: true };
            writes.prestige = { ...(writes.prestige || {}), loopInsight: insightGained };
            writes.game = { day, hour };
            applyTickWrites(writes, storeSetters);
            return;
          }

          if (day > MAX_DAY) {
            const insightGained = calcInsight(insightParams, disciplineEffects);
            addLog('The loop ends. Gained ' + insightGained + ' Insight.');
            writes.ui = { ...(writes.ui || {}), gameOver: true, victory: false };
            writes.prestige = { ...(writes.prestige || {}), loopInsight: insightGained };
            writes.game = { day, hour };
            applyTickWrites(writes, storeSetters);
            return;
          }

          const incursionStrength = getIncursionStrength(day, hour);

          let meditateTicks = ctx.mana.meditateTicks;
          let meditationMultiplier = 1;

          if (ctx.combat.currentAction === 'meditate') {
            meditateTicks++;
            meditationMultiplier = getMeditationBonus(meditateTicks, 1, disciplineEffects.meditationCapBonus);
          } else {
            meditateTicks = 0;
          }

          // ── Unified Conversion System ─────────────────────────────────────
          const { pactElementMap, grossRegen } = buildConversionParams(ctx.prestige.signedPacts, ctx.attunement.attunements);
          const invokerLevel = ctx.attunement.attunements.invoker?.active ? (ctx.attunement.attunements.invoker.level || 1) : 0;
          const conversionResult = computeConversionRates({
            disciplineEffects, attunements: ctx.attunement.attunements,
            signedPacts: ctx.prestige.signedPacts, pactElementMap, invokerLevel,
            meditationMultiplier, grossRegen, rawGrossRegen: baseRegen,
          });

          // Apply conversion results: produce element mana from conversions
          let rawMana = ctx.mana.rawMana;
          let elements = { ...ctx.mana.elements };

          // Log paused conversions (only on state change to avoid flooding the log)
          for (const [elem, entry] of Object.entries(conversionResult.rates)) {
            if (entry.paused && entry.pauseReason) {
              if (!loggedPausedConversions.has(elem)) {
                loggedPausedConversions.add(elem);
                addLog(`⚠️ PAUSED: ${elem} conversion — ${entry.pauseReason}`);
              }
            }
          }
          // Clean up entries for conversions that are no longer paused
          for (const elem of loggedPausedConversions) {
            const rateEntry = conversionResult.rates[elem];
            if (!rateEntry || !rateEntry.paused) {
              loggedPausedConversions.delete(elem);
            }
          }

          // Compute per-element net regen: produced rate - drain from being used as component
          const elementRegen: Record<string, number> = {};
          for (const [elem, entry] of Object.entries(conversionResult.rates)) {
            const produced = entry.finalRate;
            const drained = conversionResult.elementDrain[elem] || 0;
            elementRegen[elem] = produced - drained;
          }
          // Apply net element regen to pools: produced - drained (component consumption)
          for (const [elem, entry] of Object.entries(conversionResult.rates)) {
            if (entry.paused || !elements[elem] || !elements[elem].unlocked) continue;
            const netRate = elementRegen[elem];
            if (netRate === 0) continue;
            const delta = netRate * HOURS_PER_TICK;
            const newCurrent = delta >= 0
              ? Math.min(elements[elem].max, elements[elem].current + delta)
              : Math.max(0, elements[elem].current + delta);
            elements[elem] = { ...elements[elem], current: newCurrent };
          }
          // Net raw regen = gross regen - conversion drains - incursion
          const netRawRegen = Math.max(0, baseRegen * (1 - incursionStrength) * meditationMultiplier - conversionResult.totalRawDrain);
          const actualRegen = Math.floor(Math.min(netRawRegen * HOURS_PER_TICK, maxMana - rawMana) * 1000) / 1000;
          rawMana = Math.max(0, Math.min(rawMana + netRawRegen * HOURS_PER_TICK, maxMana));
          let totalManaGathered = ctx.mana.totalManaGathered + Math.max(0, actualRegen);

          const pactResult = processPactRitual(ctx.prestige.pactRitualFloor, ctx.prestige.pactRitualProgress, ctx.prestige.prestigeUpgrades.pactAffinity || 0, disciplineEffects.bonuses.pactAffinityBonus || 0);
          if (pactResult.writes) writes.prestige = { ...(writes.prestige || {}), ...pactResult.writes };
          if (pactResult.completed) {
            usePrestigeStore.getState().completePactRitual(addLog);
          }

          // Skip discipline processing during spire runs (defense-in-depth)
          const dr = ctx.combat.spireMode
            ? { rawMana, elements, unlockedEffects: [], unlockedRecipes: [], autoPausedNames: [] }
            : useDisciplineStore.getState().processTick({ rawMana, elements });
          rawMana = dr.rawMana; elements = dr.elements;
          rawMana = Math.min(rawMana, computeMaxMana({ prestigeUpgrades: ctx.prestige.prestigeUpgrades }, undefined, computeDisciplineEffects()));

          if (dr.unlockedEffects.length > 0) {
            useCraftingStore.getState().unlockEffects(dr.unlockedEffects);
            for (const effectId of dr.unlockedEffects) {
              addLog('Discipline insight unlocked: ' + effectId);
            }
          }
          if (dr.unlockedRecipes.length > 0) {
            useCraftingStore.getState().unlockRecipes(dr.unlockedRecipes);
            for (const recipeId of dr.unlockedRecipes) {
              addLog('Fabricator recipe unlocked: ' + recipeId);
            }
          }

          const perElementCapBonuses = mergePerElementCapBonuses(
            disciplineEffects.bonuses,
            equipmentEffects.bonuses,
          );
          useManaStore.getState().computeElementMaxWithBonuses(perElementCapBonuses);

          const manaStateAfter = useManaStore.getState();
          for (const [ek, es] of Object.entries(manaStateAfter.elements)) {
            if (elements[ek]) {
              elements[ek] = { ...elements[ek], max: es.max, baseMax: es.baseMax };
            }
          }

          // Combat
          if (ctx.combat.currentAction === 'climb') {
            const combatCbs = buildCombatCallbacks({ ctx, effects, maxMana, addLog, useCombatStore, usePrestigeStore });
            // Guardian regen once per tick (not per-damage-event, spec §5.3, D-25 fix)
            combatCbs.applyGuardianRegen();
            const roomEnemies = ctx.combat.currentRoom?.enemies ?? [];
            const primaryEnemy = roomEnemies[0] ?? null;
            const activeEnemy = combatCbs.applyMageBarrierRecharge(primaryEnemy) ?? primaryEnemy;
            const defCtx = { roomType: ctx.combat.currentRoom?.roomType ?? 'combat', enemy: activeEnemy };
            const golemPipeline = buildGolemCombatPipeline(addLog);
            const equippedSwords: Record<string, import('../types').EquipmentInstance> = {};
            for (const [slot, iid] of Object.entries(ctx.crafting.equippedInstances || {})) {
              if (!iid) continue;
              const inst = ctx.crafting.equipmentInstances?.[iid];
              if (inst && EQUIPMENT_TYPES[inst.typeId]?.category === 'sword') equippedSwords[iid] = inst;
            }
            // Populate equipment spell states from equipped gear
            const activeEquipSpells = getActiveEquipmentSpells(
              ctx.crafting.equippedInstances || {},
              ctx.crafting.equipmentInstances || {},
            );
            const equipmentSpellStates = activeEquipSpells.map((s) => ({
              spellId: s.spellId,
              sourceEquipment: s.equipmentId,
              castProgress: 0,
            }));
            useCombatStore.setState({ equipmentSpellStates });

            // Pact affinity cast speed bonus only applies to invocation spells (AC-12/AC-13),
            // computed locally in combat-invocation.ts. Active/equipment spells use base 1.0.
            const attackSpeedMult = 1;
            const cr = useCombatStore.getState().processCombatTick(
              rawMana, elements, maxMana, attackSpeedMult,
              combatCbs.onFloorCleared,
              combatCbs.makeOnDamageDealt(() => rawMana, () => elements, defCtx, addLog),
              ctx.prestige.signedPacts,
              { activeGolems: golemPipeline.activeGolems },
              golemPipeline.golemApplyDamageToRoom,
              (dmg, enemy, roomType, addLogFn, bypassArmor, bypassBarrier) =>
                applyEnemyDefensesFromPipeline(dmg, enemy, roomType, addLogFn, bypassArmor, bypassBarrier),
              equippedSwords,
              ctx.prestige.prestigeUpgrades.pactAffinity || 0,
            );
            rawMana = cr.rawMana; elements = cr.elements;
            totalManaGathered += cr.totalManaGathered || 0;
            if (cr.logMessages) cr.logMessages.forEach(msg => addLog(msg));
            writes.combat = { ...(writes.combat || {}), currentFloor: cr.currentFloor, floorHP: cr.floorHP, floorMaxHP: cr.floorMaxHP, maxFloorReached: cr.maxFloorReached, castProgress: cr.castProgress, weaponCastProgress: cr.weaponCastProgress, equipmentSpellStates: cr.equipmentSpellStates, golemancy: { ...(writes.combat?.golemancy || ctx.combat.golemancy), activeGolems: cr.activeGolems }, meleeSwordProgress: cr.meleeSwordProgress, currentRoom: cr.currentRoom } as Partial<CombatState>;
          }

          // Non-combat room tick (library, recovery, treasure, puzzle)
          if (ctx.combat.currentAction === 'climb') {
            const roomType = ctx.combat.currentRoom?.roomType;
            if (roomType === 'library' || roomType === 'recovery' || roomType === 'treasure' || roomType === 'puzzle') {
              if (roomType === 'recovery') {
                // Recovery room grants 10× regen/conversion multiplier.
                // Normal regen was already applied above, so we apply only
                // the delta (9× additional) to avoid double-counting.
                const boostedRegen = baseRegen * 10;
                const netBoostedRegen = Math.max(0, boostedRegen * (1 - incursionStrength) * meditationMultiplier - conversionResult.totalRawDrain);
                const regenDelta = Math.max(0, netBoostedRegen - netRawRegen);
                rawMana = Math.min(rawMana + regenDelta * HOURS_PER_TICK, maxMana);
                for (const [elem, entry] of Object.entries(conversionResult.rates)) {
                  if (entry.paused || entry.finalRate <= 0 || !elements[elem] || !elements[elem].unlocked) continue;
                  // Normal conversion already applied above; add only the 9× delta
                  elements[elem] = { ...elements[elem], current: Math.min(elements[elem].max, elements[elem].current + entry.finalRate * 9 * HOURS_PER_TICK) };
                }
              }
              useCombatStore.getState().tickNonCombatRoom(HOURS_PER_TICK);
              const updatedRoom = useCombatStore.getState().currentRoom;
              writes.combat = { ...(writes.combat || {}), currentRoom: updatedRoom };
            }
          }

          if (ctx.combat.currentAction === 'craft') {
            const craftingResult = useCraftingStore.getState().processEquipmentCraftingTick();
            if (craftingResult.logMessage) {
              addLog(craftingResult.logMessage);
            }
          }

          // Enchanting: Design / Prepare / Application ticks
          const enchantingResult = processEnchantingTicks({
            ctx, effects, rawMana, addLog,
          });
          rawMana = enchantingResult.rawMana;
          if (enchantingResult.writes) {
            if (enchantingResult.writes.combat) {
              writes.combat = { ...(writes.combat || {}), ...enchantingResult.writes.combat };
            }
            if (enchantingResult.writes.attunement) {
              writes.attunement = { ...(writes.attunement || {}), ...enchantingResult.writes.attunement };
            }
            if (enchantingResult.writes.crafting) {
              writes.crafting = { ...(writes.crafting || {}), ...enchantingResult.writes.crafting };
            }
          }

          // Phase 3: Write
          writes.game = { day, hour, incursionStrength };
          writes.mana = { rawMana, meditateTicks, totalManaGathered, elements, elementRegen };

          applyTickWrites(writes, storeSetters);
        } catch (error: unknown) {
          try {
            const msg = error instanceof Error ? error.message : String(error);
            useUIStore.getState().addLog('Tick error: ' + msg);
          } catch {
            console.error('Tick error:', error);
          }
        }
      },

      resetGame: createResetGame(set, initialState),
      togglePause: () => {
        useUIStore.getState().togglePause();
      },
      startNewLoop: createStartNewLoop(set),
      gatherMana: createGatherMana(),
    }),
    {
      storage: createSafeStorage(),
      name: 'mana-loop-game-storage',
      version: 1,
      partialize: (state) => ({
        day: state.day,
        hour: state.hour,
        incursionStrength: state.incursionStrength,
        containmentWards: state.containmentWards,
      }),
    }
  )
);


