// ─── Discipline Store Slice ────────────────────────────────────────────────────
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
import { createSafeStorage } from '../utils/safe-persist';
import type { DisciplineState } from '../types/disciplines';
import type { ElementState } from '../types';
import {
  calculateStatBonus,
  calculateManaDrain,
  canProceedDiscipline,
  checkDisciplinePrerequisites,
  getUnlockedPerks
} from '../utils/discipline-math';
import { baseDisciplines } from '../data/disciplines/base';
import { elementalAttunementDisciplines } from '../data/disciplines/elemental';
import { elementalRegenDisciplines } from '../data/disciplines/elemental-regen';
import { elementalRegenAdvancedDisciplines } from '../data/disciplines/elemental-regen-advanced';
import { enchanterDisciplines } from '../data/disciplines/enchanter';
import { enchanterUtilityDisciplines } from '../data/disciplines/enchanter-utility';
import { enchanterSpellDisciplines } from '../data/disciplines/enchanter-spells';
import { enchanterSpecialDisciplines } from '../data/disciplines/enchanter-special';
import { fabricatorDisciplines } from '../data/disciplines/fabricator';
import { invokerDisciplines } from '../data/disciplines/invoker';
import { MAX_CONCURRENT_DISCIPLINES } from '../types/disciplines';
import { useManaStore } from './manaStore';
import { usePrestigeStore } from './prestigeStore';


const ALL_DISCIPLINES = [
  ...baseDisciplines,
  ...elementalAttunementDisciplines,
  ...elementalRegenDisciplines,
  ...elementalRegenAdvancedDisciplines,
  ...enchanterDisciplines,
  ...enchanterUtilityDisciplines,
  ...enchanterSpellDisciplines,
  ...enchanterSpecialDisciplines,
  ...fabricatorDisciplines,
  ...invokerDisciplines,
];
const DISCIPLINE_MAP = Object.fromEntries(ALL_DISCIPLINES.map((d) => [d.id, d]));

export interface DisciplineStoreState {
  disciplines: Record<string, DisciplineState>;
  activeIds: string[];
  concurrentLimit: number;
  totalXP: number;
  processedPerks: string[];
  practicingCallbacks: { onStartPracticing: () => void; onStopPracticing: () => void } | null;
}

export interface DisciplineStoreActions {
  activate: (id: string, gameStateOverrides?: { elements?: Record<string, ElementState>; rawMana?: number; signedPacts?: number[] }) => void;
  deactivate: (id: string) => void;
  deactivateAll: () => void;
  processTick: (mana: { rawMana: number; elements: Record<string, ElementState> }) => {
    rawMana: number;
    elements: Record<string, ElementState>;
    unlockedEffects: string[];
    unlockedRecipes: string[];
    autoPausedNames: string[];
  };
  setPracticingCallbacks(callbacks: { onStartPracticing: () => void; onStopPracticing: () => void }): void;
  resetDisciplines: () => void;
}

export type DisciplineStore = DisciplineStoreState & DisciplineStoreActions;

export const useDisciplineStore = create<DisciplineStore>()(
  persist(
    (set, get) => ({
      disciplines: {},
      activeIds: [],
      concurrentLimit: MAX_CONCURRENT_DISCIPLINES,
      totalXP: 0,
      processedPerks: [],
      practicingCallbacks: null,

      activate(id, gameStateOverrides) {
        set((s) => {
          const def = DISCIPLINE_MAP[id];
          if (!def) return s;

          // Guard against corrupted persisted state
          const activeIds = s.activeIds ?? [];

          // Allow re-activation if discipline exists but is paused
          const existing = s.disciplines[id];
          if (activeIds.includes(id)) {
            // If already active and paused (manually or auto-paused due to insufficient mana), un-pause it
            if (existing?.paused) {
              return {
                disciplines: { ...s.disciplines, [id]: { ...existing, paused: false, autoPaused: false } },
              };
            }
            // If auto-paused due to insufficient mana, allow re-activation (un-pause when mana is restored)
            if (existing?.autoPaused) {
              return {
                disciplines: { ...s.disciplines, [id]: { ...existing, autoPaused: false } },
              };
            }
            return s;
          }

          const nonPaused = activeIds.filter((aid) => {
            const d = s.disciplines[aid];
            return d && !d.paused;
          }).length;
          if (nonPaused >= s.concurrentLimit) return s;

          // Resolve game state: use overrides if provided (for tests), otherwise read
          // directly from the mana and prestige stores. This prevents the UI from needing
          // to manually construct and pass a gameState bag — eliminating the class of bug
          // where a missing field (e.g. rawMana) silently prevents reactivation.
          const resolvedState = gameStateOverrides ?? {
            rawMana: useManaStore.getState().rawMana,
            elements: useManaStore.getState().elements,
            signedPacts: usePrestigeStore.getState().signedPacts,
          };

          if (!canProceedDiscipline(def, existing, resolvedState)) return s;

          // Check discipline prerequisites (requires field → discipline XP or mana type unlock)
          const prereqCheck = checkDisciplinePrerequisites(def, s.disciplines, ALL_DISCIPLINES, resolvedState.elements, resolvedState.signedPacts);
          if (!prereqCheck.canProceed) return s;

          // For conversion disciplines: gate on having all source mana types unlocked
          if (def.sourceManaTypes && def.sourceManaTypes.length > 0) {
            const elements = resolvedState.elements;
            if (elements) {
              for (const srcType of def.sourceManaTypes) {
                if (srcType === 'raw') continue; // raw is always available
                const srcElem = elements[srcType];
                if (!srcElem || !srcElem.unlocked) return s;
              }
            }
          }

          const discState = existing || { id, xp: 0, paused: false };
          // Set currentAction to 'practicing' (only overrides 'meditate')
          get().practicingCallbacks?.onStartPracticing?.();
          return {
            disciplines: { ...s.disciplines, [id]: { ...discState, paused: false } },
            activeIds: [...activeIds, id],
          };
        });
      },

      deactivate(id) {
        set((s) => {
          const newActiveIds = s.activeIds.filter((aid) => aid !== id);
          // If no more active disciplines, restore currentAction to 'meditate'
          if (newActiveIds.length === 0) {
            get().practicingCallbacks?.onStopPracticing?.();
          }
          return {
            activeIds: newActiveIds,
            disciplines: s.disciplines[id]
              ? { ...s.disciplines, [id]: { ...s.disciplines[id], paused: true, autoPaused: false } }
              : s.disciplines,
          };
        });
      },

      deactivateAll() {
        set((s) => {
          const newDisciplines = { ...s.disciplines };
          for (const id of s.activeIds) {
            if (newDisciplines[id]) {
              newDisciplines[id] = { ...newDisciplines[id], paused: true };
            }
          }
          get().practicingCallbacks?.onStopPracticing?.();
          return {
            activeIds: [],
            disciplines: newDisciplines,
          };
        });
      },

      setPracticingCallbacks(callbacks) {
        set({ practicingCallbacks: callbacks });
      },

      resetDisciplines() {
        set({
          disciplines: {},
          activeIds: [],
          concurrentLimit: MAX_CONCURRENT_DISCIPLINES,
          totalXP: 0,
          processedPerks: [],
        });
      },

      processTick(mana) {
        const s = get();
        let rawMana = mana.rawMana;
        const elements = { ...mana.elements };
        let newXP = s.totalXP;
        const newDisciplines = { ...s.disciplines };
        const newUnlockedEffects: string[] = [];
        const newUnlockedRecipes: string[] = [];
        const newProcessedPerks = [...(s.processedPerks ?? [])];
        const autoPausedNames: string[] = [];

        for (const id of s.activeIds ?? []) {
          const disc = newDisciplines[id];
          if (!disc) continue;
          if (disc.paused) continue;

          // Auto-resume: if this discipline was auto-paused due to insufficient mana,
          // check whether enough mana is now available. If so, clear autoPaused and
          // process normally this tick. Mana regen runs before discipline processing
          // in the tick pipeline, so the mana values passed here are already updated.
          if (disc.autoPaused) {
            const def = DISCIPLINE_MAP[id];
            if (def) {
              const drain = calculateManaDrain(def.drainBase, disc.xp, def.difficultyFactor);
              const hasEnoughMana =
                def.manaType === 'raw'
                  ? rawMana >= drain
                  : elements[def.manaType] && elements[def.manaType].current >= drain;
              if (hasEnoughMana) {
                // Mana restored — clear autoPaused and fall through to normal processing
                newDisciplines[id] = { ...disc, autoPaused: false };
              } else {
                continue; // still not enough mana, skip
              }
            } else {
              continue; // no definition, skip
            }
          }

          const def = DISCIPLINE_MAP[id];
          if (!def) continue;

          // ── Mana drain ──────────────────────────────────────────────────
          // All disciplines (including conversion) drain from their mana pool.
          // Conversion disciplines both drain their target mana type AND
          // contribute to conversion rates.
          {
            const drain = calculateManaDrain(def.drainBase, disc.xp, def.difficultyFactor);

            // Determine which mana pool to drain from
            if (def.manaType === 'raw') {
              if (rawMana < drain) {
                // Insufficient raw mana — auto-pause
                newDisciplines[id] = { ...disc, autoPaused: true };
                autoPausedNames.push(def.name);
                continue; // skip XP accrual
              }
              rawMana = Math.max(0, rawMana - drain);
            } else {
              // Element-specific drain
              const elemState = elements[def.manaType];
              if (!elemState || elemState.current < drain) {
                // Insufficient element mana — auto-pause
                newDisciplines[id] = { ...disc, autoPaused: true };
                autoPausedNames.push(def.name);
                continue; // skip XP accrual
              }
              elements[def.manaType] = {
                ...elemState,
                current: Math.max(0, elemState.current - drain),
              };
            }
          }

          const oldXP = disc.xp;
          // Compute discipline XP bonus directly to avoid circular import
          let xpBonus = 0;
          for (const [did, dState] of Object.entries(newDisciplines)) {
            if (!dState || dState.xp <= 0) continue;
            const dDef = DISCIPLINE_MAP[did];
            if (!dDef) continue;
            // Only disciplines with disciplineXpBonus stat contribute
            if (dDef.statBonus.stat === 'disciplineXpBonus') {
              xpBonus += calculateStatBonus(
                dDef.statBonus.baseValue,
                dState.xp,
                dDef.scalingFactor
              );
            }
            // Perk bonuses for disciplineXpBonus
            const perks = getUnlockedPerks(dDef, dState.xp);
            for (const perk of perks) {
              if (perk.bonus && perk.bonus.stat === 'disciplineXpBonus') {
                xpBonus += perk.bonus.amount;
              }
            }
          }
          const xpGain = 1 + xpBonus;
          newDisciplines[id] = { ...disc, xp: disc.xp + xpGain };
          newXP += xpGain;

          // Check for newly unlocked perks that unlock effects
          if (def.perks.length > 0) {
            const oldPerks = getUnlockedPerks(def, oldXP);
            const newPerks = getUnlockedPerks(def, disc.xp + 1);
            const oldPerkIds = new Set(oldPerks.map(p => p.id));
            for (const perk of newPerks) {
              if (!oldPerkIds.has(perk.id)) {
                const perkKey = `${id}:${perk.id}`;
                if (!newProcessedPerks.includes(perkKey)) {
                  if (perk.unlocksEffects) {
                    newUnlockedEffects.push(...perk.unlocksEffects);
                  }
                  if (perk.unlocksRecipes) {
                    newUnlockedRecipes.push(...perk.unlocksRecipes);
                  }
                  newProcessedPerks.push(perkKey);
                }
              }
            }
          }
        }

        const newLimit = Math.min(
          MAX_CONCURRENT_DISCIPLINES + Math.floor(newXP / 500),
          MAX_CONCURRENT_DISCIPLINES + 3
        );

        // Check if no active disciplines remain to fire onStopPracticing
        const keepActiveIds = s.activeIds ?? [];
        if (keepActiveIds.length === 0 && s.activeIds.length > 0) {
          get().practicingCallbacks?.onStopPracticing?.();
        }

        set({
          disciplines: newDisciplines,
          activeIds: keepActiveIds,
          totalXP: newXP,
          concurrentLimit: Math.max(s.concurrentLimit, newLimit),
          processedPerks: newProcessedPerks,
        });

        return { rawMana, elements, unlockedEffects: newUnlockedEffects, unlockedRecipes: newUnlockedRecipes, autoPausedNames };
      },
    }),
    { storage: createSafeStorage(), name: 'mana-loop-discipline-store', version: 1, partialize: (state) => ({ disciplines: state.disciplines, activeIds: state.activeIds, concurrentLimit: state.concurrentLimit, totalXP: state.totalXP, processedPerks: state.processedPerks }) }
  )
);