/**
 * Integration tests for batch queue execution scenarios
 *
 * These tests simulate ComfyUI's execution modes to verify correct LoRA cycling behavior.
 */

import { describe, it, expect, beforeEach } from 'vitest'
import { useLoraCyclerState } from '@/composables/useLoraCyclerState'
import {
  createMockWidget,
} from '../fixtures/mockConfigs'
import { resetFetchMock } from '../setup'
import { BatchQueueSimulator } from '../utils/BatchQueueSimulator'

/**
 * Creates a test harness that mimics the LoraCyclerWidget's behavior
 */
function createTestHarness(options: {
  totalCount?: number
  initialIndex?: number
  repeatCount?: number
  isPaused?: boolean
} = {}) {
  const {
    totalCount = 5,
    initialIndex = 1,
    repeatCount = 1,
    isPaused = false
  } = options

  const widget = createMockWidget() as any
  const state = useLoraCyclerState(widget)

  // Initialize state
  state.totalCount.value = totalCount
  state.currentIndex.value = initialIndex
  state.repeatCount.value = repeatCount
  state.isPaused.value = isPaused

  // Track if first execution
  const HAS_EXECUTED = Symbol('HAS_EXECUTED')
  widget[HAS_EXECUTED] = false

  // Execution queue for batch synchronization
  interface ExecutionContext {
    isPaused: boolean
    repeatUsed: number
    repeatCount: number
    shouldAdvanceDisplay: boolean
    displayRepeatUsed: number  // Value to show in UI after completion
  }
  const executionQueue: ExecutionContext[] = []

  // beforeQueued hook (mirrors LoraCyclerWidget.vue logic)
  widget.beforeQueued = () => {
    if (state.isPaused.value) {
      executionQueue.push({
        isPaused: true,
        repeatUsed: state.repeatUsed.value,
        repeatCount: state.repeatCount.value,
        shouldAdvanceDisplay: false,
        displayRepeatUsed: state.displayRepeatUsed.value  // Keep current display value when paused
      })
      // CRITICAL: Clear execution_index when paused to force backend to use current_index
      const pausedConfig = state.buildConfig()
      pausedConfig.execution_index = null
      widget.value = pausedConfig
      return
    }

    if (widget[HAS_EXECUTED]) {
      if (state.repeatUsed.value < state.repeatCount.value) {
        state.repeatUsed.value++
      } else {
        state.repeatUsed.value = 1
        state.generateNextIndex()
      }
    } else {
      state.repeatUsed.value = 1
      state.initializeNextIndex()
      widget[HAS_EXECUTED] = true
    }

    const shouldAdvanceDisplay = state.repeatUsed.value >= state.repeatCount.value
    // Calculate the display value to show after this execution completes
    // When advancing to a new LoRA: reset to 0 (fresh start for new LoRA)
    // When repeating same LoRA: show current repeat step
    const displayRepeatUsed = shouldAdvanceDisplay ? 0 : state.repeatUsed.value

    executionQueue.push({
      isPaused: false,
      repeatUsed: state.repeatUsed.value,
      repeatCount: state.repeatCount.value,
      shouldAdvanceDisplay,
      displayRepeatUsed
    })

    widget.value = state.buildConfig()
  }

  // Mock node with onExecuted
  const node = {
    id: 1,
    onExecuted: (output: any) => {
      const context = executionQueue.shift()

      const shouldAdvanceDisplay = context
        ? context.shouldAdvanceDisplay
        : (!state.isPaused.value && state.repeatUsed.value >= state.repeatCount.value)

      // Update displayRepeatUsed (deferred like index updates)
      if (context) {
        state.displayRepeatUsed.value = context.displayRepeatUsed
      }

      if (shouldAdvanceDisplay && output?.next_index !== undefined) {
        const val = Array.isArray(output.next_index) ? output.next_index[0] : output.next_index
        state.currentIndex.value = val
      }
      if (output?.total_count !== undefined) {
        const val = Array.isArray(output.total_count) ? output.total_count[0] : output.total_count
        state.totalCount.value = val
      }
      if (shouldAdvanceDisplay) {
        if (output?.next_lora_name !== undefined) {
          const val = Array.isArray(output.next_lora_name) ? output.next_lora_name[0] : output.next_lora_name
          state.currentLoraName.value = val
        }
        if (output?.next_lora_filename !== undefined) {
          const val = Array.isArray(output.next_lora_filename) ? output.next_lora_filename[0] : output.next_lora_filename
          state.currentLoraFilename.value = val
        }
      }
    }
  }

  // Reset execution state (mimics manual index change)
  const resetExecutionState = () => {
    widget[HAS_EXECUTED] = false
    state.executionIndex.value = null
    state.nextIndex.value = null
    executionQueue.length = 0
  }

  return {
    widget,
    state,
    node,
    executionQueue,
    resetExecutionState,
    getConfig: () => state.buildConfig(),
    HAS_EXECUTED
  }
}

describe('Batch Queue Integration Tests', () => {
  beforeEach(() => {
    resetFetchMock()
  })

  describe('Basic Cycling', () => {
    it('should cycle through N LoRAs in batch of N (batch queue mode)', async () => {
      const harness = createTestHarness({ totalCount: 3 })
      const simulator = new BatchQueueSimulator({ totalCount: 3 })

      // Simulate batch queue of 3 prompts
      await simulator.runBatchQueue(
        3,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // After cycling through all 3, currentIndex should wrap back to 1
      // First execution: index 1, next becomes 2
      // Second execution: index 2, next becomes 3
      // Third execution: index 3, next becomes 1
      expect(harness.state.currentIndex.value).toBe(1)
    })

    it('should cycle through N LoRAs in batch of N (sequential mode)', async () => {
      const harness = createTestHarness({ totalCount: 3 })
      const simulator = new BatchQueueSimulator({ totalCount: 3 })

      // Simulate sequential execution of 3 prompts
      await simulator.runSequential(
        3,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // Same result as batch mode
      expect(harness.state.currentIndex.value).toBe(1)
    })

    it('should handle partial cycle (batch of 2 in pool of 5)', async () => {
      const harness = createTestHarness({ totalCount: 5, initialIndex: 1 })
      const simulator = new BatchQueueSimulator({ totalCount: 5 })

      await simulator.runBatchQueue(
        2,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // After 2 executions starting from 1: 1 -> 2 -> 3
      expect(harness.state.currentIndex.value).toBe(3)
    })
  })

  describe('Repeat Functionality', () => {
    it('should repeat each LoRA repeatCount times', async () => {
      const harness = createTestHarness({ totalCount: 3, repeatCount: 2 })
      const simulator = new BatchQueueSimulator({ totalCount: 3 })

      // With repeatCount=2, need 6 executions to cycle through 3 LoRAs
      await simulator.runBatchQueue(
        6,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // Should have cycled back to beginning
      expect(harness.state.currentIndex.value).toBe(1)
    })

    it('should track repeatUsed correctly during batch', async () => {
      const harness = createTestHarness({ totalCount: 3, repeatCount: 3 })

      // First beforeQueued: repeatUsed = 1
      harness.widget.beforeQueued()
      expect(harness.state.repeatUsed.value).toBe(1)

      // Second beforeQueued: repeatUsed = 2
      harness.widget.beforeQueued()
      expect(harness.state.repeatUsed.value).toBe(2)

      // Third beforeQueued: repeatUsed = 3 (will advance on next)
      harness.widget.beforeQueued()
      expect(harness.state.repeatUsed.value).toBe(3)

      // Fourth beforeQueued: repeatUsed resets to 1, index advances
      harness.widget.beforeQueued()
      expect(harness.state.repeatUsed.value).toBe(1)
      expect(harness.state.nextIndex.value).toBe(3) // Advanced from 2 to 3
    })

    it('should not advance display until repeat cycle completes', async () => {
      const harness = createTestHarness({ totalCount: 5, repeatCount: 2 })
      const simulator = new BatchQueueSimulator({ totalCount: 5 })

      // First execution: repeatUsed=1 < repeatCount=2, shouldAdvanceDisplay=false
      // Second execution: repeatUsed=2 >= repeatCount=2, shouldAdvanceDisplay=true

      const indexHistory: number[] = []

      // Override onExecuted to track index changes
      const originalOnExecuted = harness.node.onExecuted
      harness.node.onExecuted = (output: any) => {
        originalOnExecuted(output)
        indexHistory.push(harness.state.currentIndex.value)
      }

      await simulator.runBatchQueue(
        4,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // Index should only change on 2nd and 4th execution
      // Starting at 1: stay 1, advance to 2, stay 2, advance to 3
      expect(indexHistory).toEqual([1, 2, 2, 3])
    })

    it('should defer displayRepeatUsed updates until workflow completion', async () => {
      const harness = createTestHarness({ totalCount: 3, repeatCount: 3 })

      // Initial state
      expect(harness.state.displayRepeatUsed.value).toBe(0)

      // Queue 3 executions in batch mode (all beforeQueued before any onExecuted)
      harness.widget.beforeQueued()  // repeatUsed = 1
      harness.widget.beforeQueued()  // repeatUsed = 2
      harness.widget.beforeQueued()  // repeatUsed = 3

      // displayRepeatUsed should NOT have changed yet (still 0)
      // because no onExecuted has been called
      expect(harness.state.displayRepeatUsed.value).toBe(0)

      // Now simulate workflow completions
      harness.node.onExecuted({ next_index: 1 })
      expect(harness.state.displayRepeatUsed.value).toBe(1)

      harness.node.onExecuted({ next_index: 1 })
      expect(harness.state.displayRepeatUsed.value).toBe(2)

      harness.node.onExecuted({ next_index: 2 })
      // After completing repeat cycle, displayRepeatUsed resets to 0
      expect(harness.state.displayRepeatUsed.value).toBe(0)
    })

    it('should reset displayRepeatUsed to 0 when advancing to new LoRA', async () => {
      const harness = createTestHarness({ totalCount: 3, repeatCount: 2 })
      const simulator = new BatchQueueSimulator({ totalCount: 3 })

      const displayHistory: number[] = []

      const originalOnExecuted = harness.node.onExecuted
      harness.node.onExecuted = (output: any) => {
        originalOnExecuted(output)
        displayHistory.push(harness.state.displayRepeatUsed.value)
      }

      // Run 4 executions: 2 repeats of LoRA 1, 2 repeats of LoRA 2
      await simulator.runBatchQueue(
        4,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // displayRepeatUsed should show:
      // 1st exec: 1 (first repeat of LoRA 1)
      // 2nd exec: 0 (complete, reset for next LoRA)
      // 3rd exec: 1 (first repeat of LoRA 2)
      // 4th exec: 0 (complete, reset for next LoRA)
      expect(displayHistory).toEqual([1, 0, 1, 0])
    })

    it('should show current repeat step when not advancing', async () => {
      const harness = createTestHarness({ totalCount: 3, repeatCount: 4 })
      const simulator = new BatchQueueSimulator({ totalCount: 3 })

      const displayHistory: number[] = []

      const originalOnExecuted = harness.node.onExecuted
      harness.node.onExecuted = (output: any) => {
        originalOnExecuted(output)
        displayHistory.push(harness.state.displayRepeatUsed.value)
      }

      // Run 4 executions: all 4 repeats of the same LoRA
      await simulator.runBatchQueue(
        4,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // displayRepeatUsed should show:
      // 1st exec: 1 (repeat 1/4, not advancing)
      // 2nd exec: 2 (repeat 2/4, not advancing)
      // 3rd exec: 3 (repeat 3/4, not advancing)
      // 4th exec: 0 (repeat 4/4, complete, reset for next LoRA)
      expect(displayHistory).toEqual([1, 2, 3, 0])
    })
  })

  describe('Pause Functionality', () => {
    it('should maintain index when paused', async () => {
      const harness = createTestHarness({ totalCount: 5, isPaused: true })
      const simulator = new BatchQueueSimulator({ totalCount: 5 })

      await simulator.runBatchQueue(
        3,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // Index should not advance when paused
      expect(harness.state.currentIndex.value).toBe(1)
    })

    it('should not count paused executions toward repeat limit', async () => {
      const harness = createTestHarness({ totalCount: 5, repeatCount: 2 })

      // Run 2 executions while paused
      harness.state.isPaused.value = true
      harness.widget.beforeQueued()
      harness.widget.beforeQueued()

      // repeatUsed should still be 0 (paused executions don't count)
      expect(harness.state.repeatUsed.value).toBe(0)

      // Unpause and run
      harness.state.isPaused.value = false
      harness.widget.beforeQueued()
      expect(harness.state.repeatUsed.value).toBe(1)
    })

    it('should preserve displayRepeatUsed when paused', async () => {
      const harness = createTestHarness({ totalCount: 5, repeatCount: 3 })

      // Run one execution to set displayRepeatUsed
      harness.widget.beforeQueued()
      harness.node.onExecuted({ next_index: 1 })
      expect(harness.state.displayRepeatUsed.value).toBe(1)

      // Pause
      harness.state.isPaused.value = true

      // Queue and execute while paused
      harness.widget.beforeQueued()
      harness.node.onExecuted({ next_index: 1 })

      // displayRepeatUsed should remain at 1 (paused executions don't change it)
      expect(harness.state.displayRepeatUsed.value).toBe(1)

      // Queue another paused execution
      harness.widget.beforeQueued()
      harness.node.onExecuted({ next_index: 1 })

      // Still should be 1
      expect(harness.state.displayRepeatUsed.value).toBe(1)
    })

    it('should use same LoRA when pause is toggled mid-batch', async () => {
      // This tests the critical bug scenario:
      // 1. User queues multiple prompts (not paused)
      // 2. All beforeQueued calls complete, each advancing execution_index
      // 3. User clicks pause
      // 4. onExecuted starts firing - paused executions should use current_index, not execution_index
      const harness = createTestHarness({ totalCount: 5 })

      // Queue first prompt (not paused) - this sets up execution_index
      harness.widget.beforeQueued()
      const config1 = harness.getConfig()
      expect(config1.execution_index).toBeNull() // First execution uses current_index

      // User clicks pause mid-batch
      harness.state.isPaused.value = true

      // Queue subsequent prompts while paused
      harness.widget.beforeQueued()
      const config2 = harness.getConfig()
      // CRITICAL: execution_index should be null when paused to force backend to use current_index
      expect(config2.execution_index).toBeNull()

      harness.widget.beforeQueued()
      const config3 = harness.getConfig()
      expect(config3.execution_index).toBeNull()

      // Verify execution queue has correct context
      expect(harness.executionQueue.length).toBe(3)
      expect(harness.executionQueue[0].isPaused).toBe(false)
      expect(harness.executionQueue[1].isPaused).toBe(true)
      expect(harness.executionQueue[2].isPaused).toBe(true)
    })

    it('should have null execution_index in widget.value when paused even after non-paused queues', async () => {
      // More detailed test for the execution_index clearing behavior
      // This tests that widget.value (what backend receives) has null execution_index
      const harness = createTestHarness({ totalCount: 5 })

      // Queue 3 prompts while not paused
      harness.widget.beforeQueued()
      harness.widget.beforeQueued()
      harness.widget.beforeQueued()

      // Verify execution_index was set by non-paused queues in widget.value
      expect(harness.widget.value.execution_index).not.toBeNull()

      // User pauses
      harness.state.isPaused.value = true

      // Queue while paused - should clear execution_index in widget.value
      // This is the value that gets sent to the backend
      harness.widget.beforeQueued()
      expect(harness.widget.value.execution_index).toBeNull()

      // State's executionIndex may still have the old value (that's fine)
      // What matters is widget.value which is what the backend uses
    })

it('should have hasQueuedPrompts true when execution queue has items', async () => {
      // This tests the pause button disabled state
      const harness = createTestHarness({ totalCount: 5 })

      // Initially no queued prompts
      expect(harness.executionQueue.length).toBe(0)

      // Queue some prompts
      harness.widget.beforeQueued()
      harness.widget.beforeQueued()
      harness.widget.beforeQueued()

      // Execution queue should have items
      expect(harness.executionQueue.length).toBe(3)
    })

    it('should have empty execution queue after all executions complete', async () => {
      // This tests that pause button becomes enabled after executions complete
      const harness = createTestHarness({ totalCount: 5 })
      const simulator = new BatchQueueSimulator({ totalCount: 5 })

      // Run batch queue execution
      await simulator.runBatchQueue(
        3,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // After all executions, queue should be empty
      expect(harness.executionQueue.length).toBe(0)
    })

    it('should resume cycling after unpause', async () => {
      const harness = createTestHarness({ totalCount: 3, initialIndex: 2 })
      const simulator = new BatchQueueSimulator({ totalCount: 3 })

      // Execute once while not paused
      await simulator.runSingle(
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // Pause
      harness.state.isPaused.value = true

      // Execute twice while paused
      await simulator.runBatchQueue(
        2,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // Unpause and execute
      harness.state.isPaused.value = false

      await simulator.runSingle(
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // Should continue from where it left off (index 3 -> 1)
      expect(harness.state.currentIndex.value).toBe(1)
    })
  })

  describe('Manual Index Change', () => {
    it('should reset execution state on manual index change', async () => {
      const harness = createTestHarness({ totalCount: 5 })

      // Execute a few times
      harness.widget.beforeQueued()
      harness.widget.beforeQueued()

      expect(harness.widget[harness.HAS_EXECUTED]).toBe(true)
      expect(harness.executionQueue.length).toBe(2)

      // User manually changes index (mimics handleIndexUpdate)
      harness.resetExecutionState()
      harness.state.setIndex(4)

      expect(harness.widget[harness.HAS_EXECUTED]).toBe(false)
      expect(harness.state.executionIndex.value).toBeNull()
      expect(harness.state.nextIndex.value).toBeNull()
      expect(harness.executionQueue.length).toBe(0)
    })

    it('should start fresh cycle from manual index', async () => {
      const harness = createTestHarness({ totalCount: 5 })
      const simulator = new BatchQueueSimulator({ totalCount: 5 })

      // Execute twice starting from 1
      await simulator.runBatchQueue(
        2,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      expect(harness.state.currentIndex.value).toBe(3)

      // User manually sets index to 1
      harness.resetExecutionState()
      harness.state.setIndex(1)

      // Execute again - should start fresh from 1
      await simulator.runBatchQueue(
        2,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      expect(harness.state.currentIndex.value).toBe(3)
    })
  })

  describe('Execution Queue Mismatch', () => {
    it('should handle interrupted execution (queue > executed)', async () => {
      const harness = createTestHarness({ totalCount: 5 })
      const simulator = new BatchQueueSimulator({ totalCount: 5 })

      // Queue 5 but only execute 2 (simulates cancel)
      await simulator.runInterrupted(
        5, // queued
        2, // executed
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // 3 contexts remain in queue
      expect(harness.executionQueue.length).toBe(3)

      // Index should reflect only the 2 executions that completed
      expect(harness.state.currentIndex.value).toBe(3)
    })

    it('should recover from mismatch on next manual index change', async () => {
      const harness = createTestHarness({ totalCount: 5 })
      const simulator = new BatchQueueSimulator({ totalCount: 5 })

      // Create mismatch
      await simulator.runInterrupted(
        5,
        2,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      expect(harness.executionQueue.length).toBe(3)

      // Manual index change clears queue
      harness.resetExecutionState()
      harness.state.setIndex(1)

      expect(harness.executionQueue.length).toBe(0)

      // Can execute normally again
      await simulator.runSingle(
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      expect(harness.state.currentIndex.value).toBe(2)
    })
  })

  describe('Edge Cases', () => {
    it('should handle single item pool', async () => {
      const harness = createTestHarness({ totalCount: 1 })
      const simulator = new BatchQueueSimulator({ totalCount: 1 })

      await simulator.runBatchQueue(
        3,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // Should always stay at index 1
      expect(harness.state.currentIndex.value).toBe(1)
    })

    it('should handle empty pool gracefully', async () => {
      const harness = createTestHarness({ totalCount: 0 })

      // beforeQueued should still work without errors
      expect(() => harness.widget.beforeQueued()).not.toThrow()
    })

    it('should handle rapid sequential executions', async () => {
      const harness = createTestHarness({ totalCount: 5 })
      const simulator = new BatchQueueSimulator({ totalCount: 5 })

      // Run 20 sequential executions
      await simulator.runSequential(
        20,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      // 20 % 5 = 0, so should wrap back to 1
      // But first execution uses index 1, so after 20 executions we're at 21 % 5 = 1
      expect(harness.state.currentIndex.value).toBe(1)
    })

    it('should preserve state consistency across many cycles', async () => {
      const harness = createTestHarness({ totalCount: 3, repeatCount: 2 })
      const simulator = new BatchQueueSimulator({ totalCount: 3 })

      // Run 100 executions in batches
      for (let batch = 0; batch < 10; batch++) {
        await simulator.runBatchQueue(
          10,
          {
            beforeQueued: () => harness.widget.beforeQueued(),
            onExecuted: (output) => harness.node.onExecuted(output)
          },
          () => harness.getConfig()
        )
      }

      // Verify state is still valid
      expect(harness.state.currentIndex.value).toBeGreaterThanOrEqual(1)
      expect(harness.state.currentIndex.value).toBeLessThanOrEqual(3)
      expect(harness.state.repeatUsed.value).toBeGreaterThanOrEqual(1)
      expect(harness.state.repeatUsed.value).toBeLessThanOrEqual(2)
      expect(harness.executionQueue.length).toBe(0)
    })
  })

  describe('Invariant Assertions', () => {
    it('should always have valid index (1 <= currentIndex <= totalCount)', async () => {
      const harness = createTestHarness({ totalCount: 5 })
      const simulator = new BatchQueueSimulator({ totalCount: 5 })

      const checkInvariant = () => {
        const { currentIndex, totalCount } = harness.state
        if (totalCount.value > 0) {
          expect(currentIndex.value).toBeGreaterThanOrEqual(1)
          expect(currentIndex.value).toBeLessThanOrEqual(totalCount.value)
        }
      }

      // Override onExecuted to check invariant after each execution
      const originalOnExecuted = harness.node.onExecuted
      harness.node.onExecuted = (output: any) => {
        originalOnExecuted(output)
        checkInvariant()
      }

      await simulator.runBatchQueue(
        20,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )
    })

    it('should always have repeatUsed <= repeatCount', async () => {
      const harness = createTestHarness({ totalCount: 5, repeatCount: 3 })

      const checkInvariant = () => {
        expect(harness.state.repeatUsed.value).toBeLessThanOrEqual(harness.state.repeatCount.value)
      }

      // Check after each beforeQueued
      for (let i = 0; i < 20; i++) {
        harness.widget.beforeQueued()
        checkInvariant()
      }
    })

    it('should consume all execution contexts (queue empty after matching executions)', async () => {
      const harness = createTestHarness({ totalCount: 5 })
      const simulator = new BatchQueueSimulator({ totalCount: 5 })

      await simulator.runBatchQueue(
        7,
        {
          beforeQueued: () => harness.widget.beforeQueued(),
          onExecuted: (output) => harness.node.onExecuted(output)
        },
        () => harness.getConfig()
      )

      expect(harness.executionQueue.length).toBe(0)
    })
  })

  describe('Batch vs Sequential Mode Equivalence', () => {
    it('should produce same final state in both modes (basic cycle)', async () => {
      // Create two identical harnesses
      const batchHarness = createTestHarness({ totalCount: 5 })
      const seqHarness = createTestHarness({ totalCount: 5 })

      const batchSimulator = new BatchQueueSimulator({ totalCount: 5 })
      const seqSimulator = new BatchQueueSimulator({ totalCount: 5 })

      // Run same number of executions in different modes
      await batchSimulator.runBatchQueue(
        7,
        {
          beforeQueued: () => batchHarness.widget.beforeQueued(),
          onExecuted: (output) => batchHarness.node.onExecuted(output)
        },
        () => batchHarness.getConfig()
      )

      await seqSimulator.runSequential(
        7,
        {
          beforeQueued: () => seqHarness.widget.beforeQueued(),
          onExecuted: (output) => seqHarness.node.onExecuted(output)
        },
        () => seqHarness.getConfig()
      )

      // Final state should be identical
      expect(batchHarness.state.currentIndex.value).toBe(seqHarness.state.currentIndex.value)
      expect(batchHarness.state.repeatUsed.value).toBe(seqHarness.state.repeatUsed.value)
      expect(batchHarness.state.displayRepeatUsed.value).toBe(seqHarness.state.displayRepeatUsed.value)
    })

    it('should produce same final state in both modes (with repeat)', async () => {
      const batchHarness = createTestHarness({ totalCount: 3, repeatCount: 2 })
      const seqHarness = createTestHarness({ totalCount: 3, repeatCount: 2 })

      const batchSimulator = new BatchQueueSimulator({ totalCount: 3 })
      const seqSimulator = new BatchQueueSimulator({ totalCount: 3 })

      await batchSimulator.runBatchQueue(
        10,
        {
          beforeQueued: () => batchHarness.widget.beforeQueued(),
          onExecuted: (output) => batchHarness.node.onExecuted(output)
        },
        () => batchHarness.getConfig()
      )

      await seqSimulator.runSequential(
        10,
        {
          beforeQueued: () => seqHarness.widget.beforeQueued(),
          onExecuted: (output) => seqHarness.node.onExecuted(output)
        },
        () => seqHarness.getConfig()
      )

      expect(batchHarness.state.currentIndex.value).toBe(seqHarness.state.currentIndex.value)
      expect(batchHarness.state.repeatUsed.value).toBe(seqHarness.state.repeatUsed.value)
      expect(batchHarness.state.displayRepeatUsed.value).toBe(seqHarness.state.displayRepeatUsed.value)
    })
  })
})
