/**
 * Property-Based Test: Selection Independence from Collapse State
 *
 * Feature: queue-collapsible-sections, Property 5: Selection Independence from Collapse State
 * **Validates: Requirements 4.2, 4.4, 4.5**
 *
 * For any combination of selected items and collapse state, the set of selected
 * item IDs remains unchanged when sections are collapsed or expanded. Select All
 * always covers all visible items regardless of which sections are collapsed.
 */
import fc from 'fast-check';

// --- Pure logic under test (mirrors IvantiTodoQueuePage behavior) ---

/**
 * Toggle a section's collapse state. Only modifies collapsedSections,
 * never touches selectedIds.
 */
function toggleSection(collapsedSections, sectionKey) {
  return { ...collapsedSections, [sectionKey]: !collapsedSections[sectionKey] };
}

/**
 * Select All — always selects all visible items regardless of collapse state.
 * The visibleItems array contains ALL pending items, not just those in expanded sections.
 */
function selectAll(visibleItems) {
  return new Set(visibleItems.map(item => item.id));
}

// --- Generators ---

const WORKFLOW_TYPES = ['CARD', 'GRANITE', 'DECOM', 'FP', 'Archer'];
const VENDORS = ['Microsoft', 'Adobe', 'Cisco', 'Unknown', 'Oracle', 'VMware'];

const queueItemArbitrary = fc.record({
  id: fc.integer({ min: 1, max: 100000 }),
  workflow_type: fc.constantFrom(...WORKFLOW_TYPES),
  vendor: fc.constantFrom(...VENDORS),
  status: fc.constant('pending'),
});

// Generate arrays of queue items with unique IDs
const queueItemsArbitrary = fc
  .array(queueItemArbitrary, { minLength: 1, maxLength: 50 })
  .map(items => {
    // Ensure unique IDs
    const seen = new Set();
    return items.filter(item => {
      if (seen.has(item.id)) return false;
      seen.add(item.id);
      return true;
    });
  })
  .filter(items => items.length > 0);

// Generate a subset of item IDs as the selected set
const selectedIdsArbitrary = (items) =>
  fc.subarray(items.map(i => i.id), { minLength: 0 }).map(ids => new Set(ids));

// Generate section keys that would exist for a given set of items
const sectionKeysForItems = (items) => {
  const keys = new Set();
  const INVENTORY_TYPES = new Set(['CARD', 'GRANITE', 'DECOM']);
  let hasInventory = false;
  for (const item of items) {
    if (INVENTORY_TYPES.has(item.workflow_type)) {
      hasInventory = true;
    } else {
      keys.add(`vendor:${item.vendor || 'Unknown'}`);
    }
  }
  if (hasInventory) keys.add('inventory');
  return [...keys];
};

// Generate arbitrary collapse state for given section keys
const collapsedSectionsArbitrary = (sectionKeys) =>
  fc.record(
    Object.fromEntries(sectionKeys.map(key => [key, fc.boolean()])),
    { withDeletedKeys: true }
  );

// --- Tests ---

describe('Property 5: Selection Independence from Collapse State', () => {
  it('toggling collapse state does not alter the set of selected item IDs', () => {
    fc.assert(
      fc.property(
        queueItemsArbitrary.chain(items =>
          fc.tuple(
            fc.constant(items),
            selectedIdsArbitrary(items),
            fc.constant(sectionKeysForItems(items))
          )
        ).chain(([items, selectedIds, sectionKeys]) =>
          fc.tuple(
            fc.constant(items),
            fc.constant(selectedIds),
            sectionKeys.length > 0
              ? collapsedSectionsArbitrary(sectionKeys)
              : fc.constant({}),
            sectionKeys.length > 0
              ? fc.array(fc.constantFrom(...sectionKeys), { minLength: 1, maxLength: 10 })
              : fc.constant([])
          )
        ),
        ([items, selectedIds, initialCollapsed, toggleSequence]) => {
          // Record the original selected IDs
          const originalSelectedIds = new Set(selectedIds);

          // Apply a sequence of toggleSection calls
          let currentCollapsed = { ...initialCollapsed };
          for (const sectionKey of toggleSequence) {
            currentCollapsed = toggleSection(currentCollapsed, sectionKey);
          }

          // The selectedIds set must remain completely unchanged
          // (toggleSection only modifies collapsedSections, never selectedIds)
          expect(selectedIds).toEqual(originalSelectedIds);
          expect(selectedIds.size).toBe(originalSelectedIds.size);
          for (const id of originalSelectedIds) {
            expect(selectedIds.has(id)).toBe(true);
          }
        }
      ),
      { numRuns: 200 }
    );
  });

  it('Select All always covers all visible items regardless of collapse state', () => {
    fc.assert(
      fc.property(
        queueItemsArbitrary.chain(items =>
          fc.tuple(
            fc.constant(items),
            fc.constant(sectionKeysForItems(items))
          )
        ).chain(([items, sectionKeys]) =>
          fc.tuple(
            fc.constant(items),
            sectionKeys.length > 0
              ? collapsedSectionsArbitrary(sectionKeys)
              : fc.constant({})
          )
        ),
        ([visibleItems, collapsedSections]) => {
          // Regardless of which sections are collapsed, selectAll operates
          // on the full visibleItems array (all pending items)
          const allSelectedIds = selectAll(visibleItems);

          // Every visible item must be in the selected set
          for (const item of visibleItems) {
            expect(allSelectedIds.has(item.id)).toBe(true);
          }

          // The selected set size must equal the number of visible items
          expect(allSelectedIds.size).toBe(visibleItems.length);

          // Verify this holds even after toggling all sections to collapsed
          let fullyCollapsed = { ...collapsedSections };
          const sectionKeys = Object.keys(collapsedSections);
          for (const key of sectionKeys) {
            fullyCollapsed = toggleSection(fullyCollapsed, key);
          }

          // selectAll still returns all visible items — collapse state is irrelevant
          const afterCollapseSelectAll = selectAll(visibleItems);
          expect(afterCollapseSelectAll).toEqual(allSelectedIds);
          expect(afterCollapseSelectAll.size).toBe(visibleItems.length);
        }
      ),
      { numRuns: 200 }
    );
  });

  it('selection count always equals total selected items across all sections regardless of collapse', () => {
    fc.assert(
      fc.property(
        queueItemsArbitrary.chain(items =>
          fc.tuple(
            fc.constant(items),
            selectedIdsArbitrary(items),
            fc.constant(sectionKeysForItems(items))
          )
        ).chain(([items, selectedIds, sectionKeys]) =>
          fc.tuple(
            fc.constant(items),
            fc.constant(selectedIds),
            sectionKeys.length > 0
              ? collapsedSectionsArbitrary(sectionKeys)
              : fc.constant({})
          )
        ),
        ([_items, selectedIds, collapsedSections]) => {
          // The selection count (selectedIds.size) is independent of collapse state.
          // Changing collapse state should never change the count.
          const countBefore = selectedIds.size;

          // Toggle every section
          let currentCollapsed = { ...collapsedSections };
          const sectionKeys = Object.keys(collapsedSections);
          for (const key of sectionKeys) {
            currentCollapsed = toggleSection(currentCollapsed, key);
            // After each toggle, selection count must remain the same
            expect(selectedIds.size).toBe(countBefore);
          }
        }
      ),
      { numRuns: 200 }
    );
  });
});
