// ─── Regression tests for Issue #382 ──────────────────────────────────────────
// Verifies that non-combat rooms (library, treasure) do NOT grant repeated
// rewards after completion, and that descent properly initializes non-combat rooms.

import { describe, it, expect, beforeEach } from 'vitest';
import { useCombatStore } from '../stores/combatStore';
import { useManaStore, makeInitialElements } from '../stores/manaStore';
import { useDisciplineStore } from '../stores/discipline-slice';
import { useUIStore } from '../stores/uiStore';
import { useGameStore } from '../stores/gameStore';
import { usePrestigeStore } from '../stores/prestigeStore';
import { tickNonCombatRoom, onEnterLibraryRoom, onEnterTreasureRoom, onEnterRecoveryRoom } from '../stores/non-combat-room-actions';
import { onEnterRoomDescend } from '../stores/combat-descent-actions';
import type { FloorState } from '../types';

// ─── Helpers ──────────────────────────────────────────────────────────────────

function makeInitialSpells() {
  return { manaBolt: { id: 'manaBolt', unlocked: true, cooldown: 0 } };
}

function makeLibraryRoom(progress = 0, required = 10): FloorState {
  return {
    roomType: 'library',
    enemies: [],
    libraryProgress: progress,
    libraryRequired: required,
    libraryStayed: false,
  };
}

function makeTreasureRoom(progress = 0, required = 10, loot = makeTestLoot()): FloorState {
  return {
    roomType: 'treasure',
    enemies: [],
    treasureProgress: progress,
    treasureRequired: required,
    treasureLoot: loot,
    treasureLootClaimed: [],
  };
}

function makeTestLoot(): FloorState['treasureLoot'] {
  return [
    { id: 'gold1', name: 'Gold', type: 'gold' as const, amount: { min: 10, max: 20 } },
    { id: 'gold2', name: 'Gold', type: 'gold' as const, amount: { min: 10, max: 20 } },
    { id: 'gold3', name: 'Gold', type: 'gold' as const, amount: { min: 10, max: 20 } },
    { id: 'gold4', name: 'Gold', type: 'gold' as const, amount: { min: 10, max: 20 } },
  ];
}

function advanceSpy() {
  const calls: Array<{ floor: number; room: number }> = [];
  const fn = () => {
    const s = useCombatStore.getState();
    calls.push({ floor: s.currentFloor, room: s.currentRoomIndex });
  };
  return { calls, fn };
}

function makeCombatStoreState(overrides: Partial<ReturnType<typeof useCombatStore.getState>> = {}) {
  return {
    currentFloor: 5,
    floorHP: 0,
    floorMaxHP: 0,
    maxFloorReached: 5,
    activeSpell: 'manaBolt',
    currentAction: 'climb' as const,
    castProgress: 0,
    spireMode: true,
    currentRoom: makeLibraryRoom(0, 10),
    clearedFloors: {} as Record<number, boolean>,
    climbDirection: 'up' as const,
    isDescending: false,
    startFloor: 1,
    exitFloor: 1,
    currentRoomIndex: 0,
    roomsPerFloor: 5,
    descentPeak: null as { floor: number; roomIndex: number } | null,
    roomResetState: {} as Record<string, boolean>,
    clearedRooms: {} as Record<string, boolean>,
    isDescentComplete: false,
    golemancy: { activeGolems: [] as [], lastSummonFloor: 0, golemDesigns: {} as Record<string, unknown>, golemLoadout: [] as [] },
    equipmentSpellStates: [] as [],
    comboHitCount: 0,
    floorHitCount: 0,
    meleeSwordProgress: {} as Record<string, number>,
    weaponCastProgress: {} as Record<string, number>,
    spells: makeInitialSpells(),
    activityLog: [] as [],
    achievements: { unlocked: [] as string[], progress: {} as Record<string, number> },
    totalSpellsCast: 0,
    totalDamageDealt: 0,
    totalCraftsCompleted: 0,
    guardianShield: 0,
    guardianShieldMax: 0,
    guardianBarrier: 0,
    guardianBarrierMax: 0,
    ...overrides,
  };
}

function resetAllStores() {
  useUIStore.setState({ paused: false, gameOver: false, victory: false, logs: [] });
  useGameStore.setState({ day: 1, hour: 0, incursionStrength: 0, containmentWards: 0, initialized: true });
  useManaStore.setState({ rawMana: 10000, meditateTicks: 0, totalManaGathered: 0, elements: makeInitialElements(500, {}) });
  usePrestigeStore.setState({
    loopCount: 0, insight: 0, totalInsight: 0, loopInsight: 0,
    prestigeUpgrades: {}, pactSlots: 1, defeatedGuardians: [],
    signedPacts: [], signedPactDetails: {},
    pactRitualFloor: null, pactRitualProgress: 0,
  });
  useDisciplineStore.setState({
    disciplines: {
      'regen-transference': {
        id: 'regen-transference',
        defId: 'regen-transference',
        xp: 100,
        active: true,
        paused: false,
        xpAccruedThisLoop: 100,
        lastPerkIndex: -1,
        perksApplied: [],
      },
    },
    activeIds: ['regen-transference'],
    concurrentLimit: 1,
    totalXP: 100,
    processedPerks: [],
  });
}

// ─── Tests ─────────────────────────────────────────────────────────────────────

describe('Issue #382 — Non-combat room reward guards', () => {
  beforeEach(() => {
    resetAllStores();
  });

  // ── Library Room ──────────────────────────────────────────────────────────

  describe('tickNonCombatRoom — library', () => {
    it('should NOT grant XP when progress >= required (already complete)', () => {
      const adv = advanceSpy();
      // Room is already complete: progress=10, required=10
      useCombatStore.setState(makeCombatStoreState({
        currentRoom: makeLibraryRoom(10, 10),
      }));

      const xpBefore = useDisciplineStore.getState().totalXP;

      tickNonCombatRoom(
        () => useCombatStore.getState() as any,
        (partial) => useCombatStore.setState(partial),
        0.04,
        adv.fn,
      );

      const xpAfter = useDisciplineStore.getState().totalXP;
      expect(xpAfter).toBe(xpBefore);
    });

    it('should grant XP when progress < required (not yet complete)', () => {
      const adv = advanceSpy();
      // Room is NOT complete: progress=0, required=10
      useCombatStore.setState(makeCombatStoreState({
        currentRoom: makeLibraryRoom(0, 10),
      }));

      const xpBefore = useDisciplineStore.getState().totalXP;

      tickNonCombatRoom(
        () => useCombatStore.getState() as any,
        (partial) => useCombatStore.setState(partial),
        0.04,
        adv.fn,
      );

      const xpAfter = useDisciplineStore.getState().totalXP;
      expect(xpAfter).toBeGreaterThan(xpBefore);
    });

    it('should NOT grant XP on ticks after the room has been completed', () => {
      const adv = advanceSpy();
      // Room starts at progress=0, required=10
      useCombatStore.setState(makeCombatStoreState({
        currentRoom: makeLibraryRoom(0, 10),
      }));

      // Tick many times to complete the room
      for (let i = 0; i < 300; i++) {
        tickNonCombatRoom(
          () => useCombatStore.getState() as any,
          (partial) => useCombatStore.setState(partial),
          0.04,
          adv.fn,
        );
      }

      // Now the room should be complete; verify no more XP is granted
      const xpAfterCompletion = useDisciplineStore.getState().totalXP;

      // Tick again — should NOT grant more XP
      tickNonCombatRoom(
        () => useCombatStore.getState() as any,
        (partial) => useCombatStore.setState(partial),
        0.04,
        adv.fn,
      );

      const xpAfterExtraTick = useDisciplineStore.getState().totalXP;
      expect(xpAfterExtraTick).toBe(xpAfterCompletion);
    });
  });

  // ── Treasure Room ─────────────────────────────────────────────────────────

  describe('tickNonCombatRoom — treasure', () => {
    it('should NOT process loot when progress >= required (already complete)', () => {
      const adv = advanceSpy();
      // Room already complete
      useCombatStore.setState(makeCombatStoreState({
        currentRoom: makeTreasureRoom(10, 10, makeTestLoot()),
      }));

      const rawManaBefore = useManaStore.getState().rawMana;

      tickNonCombatRoom(
        () => useCombatStore.getState() as any,
        (partial) => useCombatStore.setState(partial),
        0.04,
        adv.fn,
      );

      const rawManaAfter = useManaStore.getState().rawMana;
      expect(rawManaAfter).toBe(rawManaBefore);
      // advance should be called to move past the completed room
      expect(adv.calls.length).toBe(1);
    });

    it('should NOT grant new loot on successive ticks after completion', () => {
      const adv = advanceSpy();
      // Complete room with already-claimed loot
      useCombatStore.setState(makeCombatStoreState({
        currentRoom: {
          roomType: 'treasure',
          enemies: [],
          treasureProgress: 15,
          treasureRequired: 10,
          treasureLoot: makeTestLoot(),
          treasureLootClaimed: [0, 1, 2, 3],
        },
      }));

      const rawManaBefore = useManaStore.getState().rawMana;

      tickNonCombatRoom(
        () => useCombatStore.getState() as any,
        (partial) => useCombatStore.setState(partial),
        0.04,
        adv.fn,
      );

      const rawManaAfter = useManaStore.getState().rawMana;
      expect(rawManaAfter).toBe(rawManaBefore);
    });
  });

  // ── Descent: onEnterRoomDescend ───────────────────────────────────────────

  describe('onEnterRoomDescend — non-combat room initialization', () => {
    it('should initialize library room during descent', () => {
      useCombatStore.setState({
        ...makeCombatStoreState({
          currentFloor: 10,
          currentRoomIndex: 2,
          roomsPerFloor: 5,
          roomResetState: { '10:2': false },
          clearedRooms: { '10': true },
          currentRoom: makeLibraryRoom(0, 1),
          climbDirection: 'down' as const,
          isDescending: true,
          descentPeak: { floor: 10, roomIndex: 4 },
        }),
        // Override with full state
      } as any);

      onEnterRoomDescend(
        () => useCombatStore.getState() as any,
        (partial) => useCombatStore.setState(partial),
      );

      const room = useCombatStore.getState().currentRoom;
      expect(room.libraryProgress).toBe(0);
      expect(room.libraryRequired).toBe(1);
    });

    it('should initialize treasure room during descent', () => {
      useCombatStore.setState({
        ...makeCombatStoreState({
          currentFloor: 10,
          currentRoomIndex: 2,
          roomsPerFloor: 5,
          roomResetState: { '10:2': false },
          clearedRooms: { '10': true },
          currentRoom: makeTreasureRoom(0, 1, makeTestLoot()),
          climbDirection: 'down' as const,
          isDescending: true,
          descentPeak: { floor: 10, roomIndex: 4 },
        }),
      } as any);

      onEnterRoomDescend(
        () => useCombatStore.getState() as any,
        (partial) => useCombatStore.setState(partial),
      );

      const room = useCombatStore.getState().currentRoom;
      expect(room.treasureProgress).toBe(0);
      expect(room.treasureRequired).toBe(1);
    });

    it('should initialize recovery room during descent', () => {
      useCombatStore.setState({
        ...makeCombatStoreState({
          currentFloor: 10,
          currentRoomIndex: 2,
          roomsPerFloor: 5,
          roomResetState: { '10:2': false },
          clearedRooms: { '10': true },
          currentRoom: {
            roomType: 'recovery',
            enemies: [],
            recoveryProgress: 0,
            recoveryRequired: 1,
            recoveryStayed: false,
          },
          climbDirection: 'down' as const,
          isDescending: true,
          descentPeak: { floor: 10, roomIndex: 4 },
        }),
      } as any);

      onEnterRoomDescend(
        () => useCombatStore.getState() as any,
        (partial) => useCombatStore.setState(partial),
      );

      const room = useCombatStore.getState().currentRoom;
      expect(room.recoveryProgress).toBe(0);
      expect(room.recoveryRequired).toBe(1);
    });
  });
});
