/**
 * ValidationTable Component
 *
 * Renders the editable data table for product validation.
 * Uses TanStack Table for column management and virtualization for performance.
 *
 * PERFORMANCE ARCHITECTURE:
 * - Table subscribes only to: rowCount, fields, filters (NOT selectedRows!)
 * - VirtualRow subscribes only to ITS OWN selection status via fine-grained selector
 * - CellWrapper subscribes only to its own cell data
 * - columns memo does NOT depend on selection state
 * - All widths use minWidth + flexShrink:0 to enforce config.ts values
 */

import { useMemo, useRef, useCallback, memo, useState } from 'react';
import { apiFetch } from '@/utils/api';
import { type ColumnDef } from '@tanstack/react-table';
import { useVirtualizer } from '@tanstack/react-virtual';
import { Checkbox } from '@/components/ui/checkbox';
import { ArrowDown, Wand2, Loader2, Calculator, Scale, Pin, PinOff } from 'lucide-react';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { toast } from 'sonner';
import { cn } from '@/lib/utils';
import config from '@/config';
import { useValidationStore } from '../store/validationStore';
import {
  useFields,
  useFilters,
  useRowCount,
} from '../store/selectors';
// NOTE: We intentionally do NOT import useValidationActions or useProductLines here!
// Those hooks subscribe to global state (rows, errors, caches) which would cause
// ALL cells to re-render when ANY cell changes. Instead, CellWrapper gets store
// actions directly and uses getState() for one-time data access.
import { ErrorSource, ErrorType, type RowData, type ValidationError } from '../store/types';
import type { Field, SelectOption, Validation } from '../../../types';
import { correctUpcValue } from '../utils/upcUtils';
import {
  buildNameValidationPayload,
  buildDescriptionValidationPayload,
} from '../utils/inlineAiPayload';

// Copy-down banner component
import { CopyDownBanner } from './CopyDownBanner';

// Template select
import SearchableTemplateSelect from './SearchableTemplateSelect';

// Cell components
import { InputCell } from './cells/InputCell';
import { SelectCell } from './cells/SelectCell';
import { ComboboxCell } from './cells/ComboboxCell';
import { MultiSelectCell } from './cells/MultiSelectCell';
import { MultilineInput } from './cells/MultilineInput';

// AI Suggestions context
import { useAiSuggestionsContext } from '../contexts/AiSuggestionsContext';

// AI Suggestion Badge for inline validation
import { AiSuggestionBadge } from './AiSuggestionBadge';
import type { InlineAiSuggestion } from '../store/types';

// Fields that trigger AI suggestion refresh when changed
const AI_EMBEDDING_FIELDS = ['company', 'line', 'name', 'description'] as const;

// Fields that trigger inline AI validation (Groq)
const INLINE_AI_VALIDATION_FIELDS = ['name', 'description'] as const;

// Threshold for switching to ComboboxCell (with search) instead of SelectCell
const COMBOBOX_OPTION_THRESHOLD = 50;

/**
 * Get cell component based on field type and option count
 *
 * PERFORMANCE: For fields with many options (50+), use ComboboxCell which has
 * built-in search filtering. This prevents rendering 100+ SelectItem components
 * when the dropdown opens, dramatically improving click responsiveness.
 */
const getCellComponent = (field: Field<string>, optionCount: number = 0) => {
  // Check for multiline input fields first
  if (field.fieldType.type === 'input' && 'multiline' in field.fieldType && field.fieldType.multiline) {
    return MultilineInput;
  }

  switch (field.fieldType.type) {
    case 'select':
      // Use ComboboxCell for large option lists
      if (optionCount >= COMBOBOX_OPTION_THRESHOLD) {
        return ComboboxCell;
      }
      return SelectCell;
    case 'multi-select':
      return MultiSelectCell;
    default:
      return InputCell;
  }
};

/**
 * Row height for virtualization
 */
const ROW_HEIGHT = 80; // Taller rows to show 2 lines of description + space for AI badges
const HEADER_HEIGHT = 40;

// Stable empty references to avoid creating new objects in selectors
// CRITICAL: Selectors must return stable references or Zustand triggers infinite re-renders
const EMPTY_ERRORS: ValidationError[] = [];
const EMPTY_OPTIONS: SelectOption[] = [];
const EMPTY_ROW_ERRORS: Record<string, ValidationError[]> = {};

/**
 * CellWrapper props - receives data from parent VirtualRow
 * NO STORE SUBSCRIPTIONS - purely presentational
 */
interface CellWrapperProps {
  field: Field<string>;
  rowIndex: number;
  /** Product's unique __index for AI suggestions */
  productIndex: string;
  value: unknown;
  errors: ValidationError[];
  isValidating: boolean;
  // For line/subline dependent dropdowns - parent values only
  company?: unknown;
  line?: unknown;
  // For UPC generation
  supplier?: unknown;
  // Loading state for dependent dropdowns
  isLoadingOptions?: boolean;
  // Copy-down state (from parent to avoid subscriptions in every cell)
  isCopyDownActive: boolean;
  isCopyDownSource: boolean;
  isInCopyDownRange: boolean;
  isCopyDownTarget: boolean;
  totalRowCount: number;
  // Inline AI validation (Groq-powered)
  inlineAiSuggestion?: InlineAiSuggestion;
  isInlineAiValidating?: boolean;
}

/**
 * Memoized cell wrapper - PURE COMPONENT with no store subscriptions
 *
 * PERFORMANCE: All data comes from props (passed by VirtualRow).
 * Cache data for dependent dropdowns is read via getState() to avoid
 * cascading re-renders when cache updates.
 */
const CellWrapper = memo(({
  field,
  rowIndex,
  productIndex,
  value,
  errors,
  isValidating,
  company,
  line,
  supplier,
  isLoadingOptions = false,
  isCopyDownActive,
  isCopyDownSource,
  isInCopyDownRange,
  isCopyDownTarget,
  totalRowCount,
  inlineAiSuggestion,
  isInlineAiValidating = false,
}: CellWrapperProps) => {
  const [isHovered, setIsHovered] = useState(false);
  const [isGeneratingUpc, setIsGeneratingUpc] = useState(false);
  const needsCompany = field.key === 'line';
  const needsLine = field.key === 'subline';

  // AI suggestions context - for notifying when embedding fields change
  // This uses stable callbacks so it won't cause re-renders
  const aiSuggestions = useAiSuggestionsContext();
  const isEmbeddingField = AI_EMBEDDING_FIELDS.includes(field.key as typeof AI_EMBEDDING_FIELDS[number]);

  // Check if this field supports inline AI validation
  const isInlineAiField = INLINE_AI_VALIDATION_FIELDS.includes(field.key as typeof INLINE_AI_VALIDATION_FIELDS[number]);

  // Get the suggestion for this specific field
  const fieldSuggestion = isInlineAiField ? inlineAiSuggestion?.[field.key as 'name' | 'description'] : undefined;
  const isDismissed = isInlineAiField ? inlineAiSuggestion?.dismissed?.[field.key as 'name' | 'description'] : false;
  const showSuggestion = fieldSuggestion && !fieldSuggestion.isValid && fieldSuggestion.suggestion && !isDismissed;

  // Debug: Log when we have a suggestion for name field
  if (isInlineAiField && field.key === 'name' && inlineAiSuggestion) {
    console.log('[CellWrapper] Name field suggestion:', {
      productIndex,
      inlineAiSuggestion,
      fieldSuggestion,
      isDismissed,
      showSuggestion,
      isValid: fieldSuggestion?.isValid,
      suggestion: fieldSuggestion?.suggestion
    });
  }

  // Check if cell has a value (for showing copy-down button)
  const hasValue = value !== undefined && value !== null && value !== '';

  // Check if field has unique validation rule (copy-down should be disabled)
  const hasUniqueValidation = field.validations?.some((v: Validation) => v.rule === 'unique') ?? false;

  // Check if cell has errors (for positioning copy-down button)
  const hasErrors = errors.length > 0;

  // Show copy-down button when:
  // - Cell is hovered
  // - Cell has a value
  // - Not already in copy-down mode
  // - There are rows below this one
  // - Field does NOT have unique validation (can't copy unique values)
  const showCopyDownButton = isHovered && hasValue && !isCopyDownActive && rowIndex < totalRowCount - 1 && !hasUniqueValidation;

  // UPC Generation logic
  const isUpcField = field.key === 'upc';
  const upcIsEmpty = isUpcField && !hasValue;
  const supplierIdString = supplier !== undefined && supplier !== null
    ? String(supplier).trim()
    : '';
  const hasValidSupplier = /^\d+$/.test(supplierIdString);
  const showGenerateUpcButton = isHovered && upcIsEmpty && !isValidating && !isCopyDownActive && !isGeneratingUpc;

  // Handle starting copy-down
  const handleStartCopyDown = useCallback((e: React.MouseEvent) => {
    e.stopPropagation();
    useValidationStore.getState().startCopyDown(rowIndex, field.key);
  }, [rowIndex, field.key]);

  // Handle clicking on a target cell to complete copy-down
  const handleTargetClick = useCallback(() => {
    if (isCopyDownTarget) {
      useValidationStore.getState().completeCopyDown(rowIndex);
    }
  }, [isCopyDownTarget, rowIndex]);

  // Handle UPC generation
  const handleGenerateUpc = useCallback(async (e: React.MouseEvent) => {
    e.stopPropagation();

    if (!hasValidSupplier) {
      toast.error('Select a supplier before generating a UPC');
      return;
    }

    if (isGeneratingUpc) return;

    setIsGeneratingUpc(true);

    try {
      const response = await apiFetch(`${config.apiUrl}/import/generate-upc`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ supplierId: supplierIdString }),
      });

      const payload = await response.json();

      if (!response.ok) {
        throw new Error(payload?.error || `Request failed (${response.status})`);
      }

      if (!payload?.success || !payload?.upc) {
        throw new Error(payload?.error || 'Unexpected response while generating UPC');
      }

      // Update the cell value and clear any validation errors
      const { updateCell, clearFieldError, setUpcStatus, setGeneratedItemNumber,
              cacheUpcResult, getCachedItemNumber, startValidatingCell, stopValidatingCell,
              setError } = useValidationStore.getState();
      updateCell(rowIndex, 'upc', payload.upc);
      clearFieldError(rowIndex, 'upc');
      toast.success('UPC generated');

      // Trigger UPC validation to generate item number
      // We have both supplier and the newly generated UPC, so validate immediately
      const upc = payload.upc;

      // Check cache first
      const cached = getCachedItemNumber(supplierIdString, upc);
      if (cached) {
        setGeneratedItemNumber(rowIndex, cached);
      } else {
        // Start validation
        setUpcStatus(rowIndex, 'validating');
        startValidatingCell(rowIndex, 'item_number');

        try {
          const validationResponse = await apiFetch(
            `${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upc)}&supplierId=${encodeURIComponent(supplierIdString)}`
          );

          const validationPayload = await validationResponse.json().catch(() => null);

          if (validationResponse.status === 409) {
            // UPC already exists (shouldn't happen with newly generated UPC, but handle it)
            setError(rowIndex, 'upc', {
              message: 'A product with this UPC already exists',
              level: 'error',
              source: ErrorSource.Upc,
              type: ErrorType.Unique,
            });
            setUpcStatus(rowIndex, 'error');
            updateCell(rowIndex, 'item_number', '');
          } else if (validationResponse.ok && validationPayload?.success && validationPayload?.itemNumber) {
            // Success - cache and apply
            cacheUpcResult(supplierIdString, upc, validationPayload.itemNumber);
            setGeneratedItemNumber(rowIndex, validationPayload.itemNumber);
            clearFieldError(rowIndex, 'upc');
            setUpcStatus(rowIndex, 'done');
          } else {
            setUpcStatus(rowIndex, 'error');
            updateCell(rowIndex, 'item_number', '');
          }
        } catch (validationError) {
          console.error('UPC validation error:', validationError);
          setUpcStatus(rowIndex, 'error');
        } finally {
          stopValidatingCell(rowIndex, 'item_number');
        }
      }
    } catch (error) {
      console.error('Error generating UPC:', error);
      const errorMessage = error instanceof Error ? error.message : 'Failed to generate UPC';
      toast.error(errorMessage);
    } finally {
      setIsGeneratingUpc(false);
    }
  }, [hasValidSupplier, isGeneratingUpc, supplierIdString, rowIndex]);

  // Read cache via getState() - NOT a subscription
  // This avoids cascading re-renders when cache updates
  const getOptions = useCallback((): SelectOption[] => {
    if (needsCompany && company) {
      const cached = useValidationStore.getState().productLinesCache.get(String(company));
      if (cached) return cached;
    }
    if (needsLine && line) {
      const cached = useValidationStore.getState().sublinesCache.get(String(line));
      if (cached) return cached;
    }
    if ('options' in field.fieldType) {
      return field.fieldType.options as SelectOption[];
    }
    return EMPTY_OPTIONS;
  }, [needsCompany, needsLine, company, line, field.fieldType]);

  // Get initial options - will be refreshed when dropdown opens via onFetchOptions
  const options = getOptions();

  // Get cell component based on field type AND option count
  // This ensures large option lists use ComboboxCell with search
  const CellComponent = getCellComponent(field, options.length);

  // Stable callback for onChange - not used by cells anymore, but kept for API compatibility
  const handleChange = useCallback((newValue: unknown) => {
    useValidationStore.getState().updateCell(rowIndex, field.key, newValue);
  }, [rowIndex, field.key]);

  // Stable callback for onBlur - validates field and triggers UPC validation if needed
  // Uses setTimeout(0) to defer validation AFTER browser paint
  const handleBlur = useCallback((newValue: unknown) => {
    const state = useValidationStore.getState();
    const { updateCell } = state;

    // Capture previous value BEFORE updating - needed to detect actual changes
    const previousValue = state.rows[rowIndex]?.[field.key];

    let valueToSave = newValue;

    // Auto-correct UPC check digit if this is the UPC field
    if (field.key === 'upc' && typeof newValue === 'string' && newValue.trim()) {
      const { corrected, changed } = correctUpcValue(newValue);
      if (changed) {
        valueToSave = corrected;
        // We'll use the corrected value
      }
    }

    // Check if the value actually changed (for AI validation trigger)
    const valueChanged = String(valueToSave ?? '') !== String(previousValue ?? '');

    updateCell(rowIndex, field.key, valueToSave);

    // Defer validation to after the browser paints
    setTimeout(async () => {
      const { setError, clearFieldError, fields, setUpcStatus, setGeneratedItemNumber, 
              cacheUpcResult, getCachedItemNumber, startValidatingCell, stopValidatingCell } = useValidationStore.getState();

      const fieldDef = fields.find((f) => f.key === field.key);
      if (!fieldDef?.validations) {
        clearFieldError(rowIndex, field.key);
      } else {
        let hasError = false;

        // Check required validation
        for (const validation of fieldDef.validations) {
          if (validation.rule === 'required') {
            const isEmpty = valueToSave === undefined || valueToSave === null ||
              (typeof valueToSave === 'string' && valueToSave.trim() === '') ||
              (Array.isArray(valueToSave) && valueToSave.length === 0);

            if (isEmpty) {
              setError(rowIndex, field.key, {
                message: validation.errorMessage || 'This field is required',
                level: validation.level || 'error',
                source: ErrorSource.Row,
                type: ErrorType.Required,
              });
              hasError = true;
              break;
            }
          }
        }

        // Check unique validation if no required error
        if (!hasError) {
          const uniqueValidation = fieldDef.validations.find((v: Validation) => v.rule === 'unique');
          if (uniqueValidation) {
            const rows = useValidationStore.getState().rows;
            const stringValue = String(valueToSave ?? '').toLowerCase().trim();

            // Only check uniqueness if value is not empty
            if (stringValue !== '') {
              // Find ALL rows with the same value (including current row)
              const duplicateRowIndices: number[] = [];
              rows.forEach((row, idx) => {
                // For current row, use the new value being saved; for other rows, use stored value
                const cellValue = idx === rowIndex ? valueToSave : row[field.key];
                const otherValue = String(cellValue ?? '').toLowerCase().trim();
                if (otherValue === stringValue) {
                  duplicateRowIndices.push(idx);
                }
              });

              const isDuplicate = duplicateRowIndices.length > 1;

              if (isDuplicate) {
                // Set error on ALL duplicate rows (bidirectional)
                const errorObj = {
                  message: (uniqueValidation as { errorMessage?: string }).errorMessage || 'Must be unique',
                  level: (uniqueValidation as { level?: 'error' | 'warning' | 'info' }).level || 'error',
                  source: ErrorSource.Table,
                  type: ErrorType.Unique,
                };
                duplicateRowIndices.forEach((idx) => {
                  setError(idx, field.key, errorObj);
                });
                hasError = true;
              } else {
                // Value is now unique - clear any existing unique errors on other rows
                // that might have had this value before
                rows.forEach((row, idx) => {
                  if (idx !== rowIndex) {
                    const existingErrors = useValidationStore.getState().errors.get(idx);
                    const fieldErrors = existingErrors?.[field.key];
                    if (fieldErrors?.some(e => e.type === ErrorType.Unique)) {
                      // Re-validate this row's uniqueness
                      const otherValue = String(row[field.key] ?? '').toLowerCase().trim();
                      if (otherValue !== '') {
                        const stillHasDuplicate = rows.some((r, i) => {
                          if (i === idx) return false;
                          const cellValue = i === rowIndex ? valueToSave : r[field.key];
                          const v = String(cellValue ?? '').toLowerCase().trim();
                          return v === otherValue;
                        });
                        if (!stillHasDuplicate) {
                          clearFieldError(idx, field.key);
                        }
                      }
                    }
                  }
                });
              }
            }
          }
        }

        if (!hasError) {
          clearFieldError(rowIndex, field.key);
        }
      }
      
      // Trigger product lines fetch if company changed
      if (field.key === 'company' && valueToSave) {
        const companyId = String(valueToSave);
        const state = useValidationStore.getState();
        const cached = state.productLinesCache.get(companyId);

        if (!cached) {
          // Start loading state and fetch product lines
          state.setLoadingProductLines(companyId, true);
          apiFetch(`/api/import/product-lines/${companyId}`)
            .then(res => res.json())
            .then(lines => {
              const opts = lines.map((line: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
                label: line.name || line.label || String(line.value || line.id),
                value: String(line.value || line.id),
              }));
              state.setProductLines(companyId, opts);
            })
            .catch(err => {
              console.error('Error prefetching product lines:', err);
              state.setProductLines(companyId, []);
            })
            .finally(() => {
              state.setLoadingProductLines(companyId, false);
            });
        }

        // Clear line and subline when company changes (they're no longer valid)
        updateCell(rowIndex, 'line', '');
        updateCell(rowIndex, 'subline', '');
      }

      // Trigger sublines fetch if line changed
      if (field.key === 'line' && valueToSave) {
        const lineId = String(valueToSave);
        const state = useValidationStore.getState();
        const cached = state.sublinesCache.get(lineId);

        if (!cached) {
          // Start loading state and fetch sublines
          state.setLoadingSublines(lineId, true);
          apiFetch(`/api/import/sublines/${lineId}`)
            .then(res => res.json())
            .then(sublines => {
              const opts = sublines.map((subline: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
                label: subline.name || subline.label || String(subline.value || subline.id),
                value: String(subline.value || subline.id),
              }));
              state.setSublines(lineId, opts);
            })
            .catch(err => {
              console.error('Error prefetching sublines:', err);
              state.setSublines(lineId, []);
            })
            .finally(() => {
              state.setLoadingSublines(lineId, false);
            });
        }

        // Clear subline when line changes (it's no longer valid)
        updateCell(rowIndex, 'subline', '');

        // Check if row now has sufficient context for inline AI validation
        // (line was just set, check if company + name exist)
        const currentRowForContext = useValidationStore.getState().rows[rowIndex];
        if (currentRowForContext?.company && currentRowForContext?.name) {
          const { setInlineAiValidating, setInlineAiSuggestion, inlineAi, fields, rows } = useValidationStore.getState();
          const contextProductIndex = currentRowForContext.__index;

          // Check if name should be validated
          const nameSuggestion = inlineAi.suggestions.get(contextProductIndex);
          const nameIsDismissed = nameSuggestion?.dismissed?.name;
          const nameIsValidating = inlineAi.validating.has(`${contextProductIndex}-name`);
          const nameValue = String(currentRowForContext.name).trim();

          if (nameValue && !nameIsDismissed && !nameIsValidating) {
            // Trigger name validation with line override (use new line value)
            setInlineAiValidating(`${contextProductIndex}-name`, true);

            const payload = buildNameValidationPayload(currentRowForContext, fields, rows, {
              line: valueToSave as string | number,
            });

            apiFetch('/api/ai/validate/inline/name', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ product: payload }),
            })
              .then(res => res.json())
              .then(result => {
                if (result.success !== false) {
                  setInlineAiSuggestion(contextProductIndex, 'name', {
                    isValid: result.isValid ?? true,
                    suggestion: result.suggestion,
                    issues: result.issues || [],
                    latencyMs: result.latencyMs,
                  });
                }
              })
              .catch(err => console.error('[InlineAI] name validation error on line change:', err))
              .finally(() => setInlineAiValidating(`${contextProductIndex}-name`, false));
          }

          // Check if description should be validated
          const descIsDismissed = nameSuggestion?.dismissed?.description;
          const descIsValidating = inlineAi.validating.has(`${contextProductIndex}-description`);
          const descValue = currentRowForContext.description ? String(currentRowForContext.description).trim() : '';

          if (descValue.length >= 10 && !descIsDismissed && !descIsValidating) {
            // Trigger description validation
            setInlineAiValidating(`${contextProductIndex}-description`, true);

            const payload = buildDescriptionValidationPayload(currentRowForContext, fields);

            apiFetch('/api/ai/validate/inline/description', {
              method: 'POST',
              headers: { 'Content-Type': 'application/json' },
              body: JSON.stringify({ product: payload }),
            })
              .then(res => res.json())
              .then(result => {
                if (result.success !== false) {
                  setInlineAiSuggestion(contextProductIndex, 'description', {
                    isValid: result.isValid ?? true,
                    suggestion: result.suggestion,
                    issues: result.issues || [],
                    latencyMs: result.latencyMs,
                  });
                }
              })
              .catch(err => console.error('[InlineAI] description validation error on line change:', err))
              .finally(() => setInlineAiValidating(`${contextProductIndex}-description`, false));
          }
        }
      }

      // Trigger UPC validation if supplier or UPC changed
      if (field.key === 'supplier' || field.key === 'upc') {
        const currentRow = useValidationStore.getState().rows[rowIndex];
        const supplierValue = field.key === 'supplier' ? valueToSave : currentRow?.supplier;
        const upcValue = field.key === 'upc' ? valueToSave : currentRow?.upc;
        
        if (supplierValue && upcValue) {
          const supplierId = String(supplierValue);
          const upc = String(upcValue);
          
          // Check cache first
          const cached = getCachedItemNumber(supplierId, upc);
          if (cached) {
            setGeneratedItemNumber(rowIndex, cached);
            return;
          }
          
          // Start validation
          setUpcStatus(rowIndex, 'validating');
          startValidatingCell(rowIndex, 'item_number');
          
          try {
            const response = await apiFetch(
              `${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upc)}&supplierId=${encodeURIComponent(supplierId)}`
            );
            
            const payload = await response.json().catch(() => null);
            
            if (response.status === 409) {
              // UPC already exists
              setError(rowIndex, 'upc', {
                message: 'A product with this UPC already exists',
                level: 'error',
                source: ErrorSource.Upc,
                type: ErrorType.Unique,
              });
              setUpcStatus(rowIndex, 'error');
              updateCell(rowIndex, 'item_number', '');
            } else if (response.ok && payload?.success && payload?.itemNumber) {
              // Success - cache and apply
              cacheUpcResult(supplierId, upc, payload.itemNumber);
              setGeneratedItemNumber(rowIndex, payload.itemNumber);
              clearFieldError(rowIndex, 'upc');
              setUpcStatus(rowIndex, 'done');
            } else {
              setUpcStatus(rowIndex, 'error');
              updateCell(rowIndex, 'item_number', '');
            }
          } catch (error) {
            console.error('UPC validation error:', error);
            setUpcStatus(rowIndex, 'error');
          } finally {
            stopValidatingCell(rowIndex, 'item_number');
          }
        }
      }

      // Notify AI suggestions system when embedding fields change
      // This triggers a refresh of category/theme/color suggestions
      if (isEmbeddingField && aiSuggestions) {
        const currentRow = useValidationStore.getState().rows[rowIndex];
        if (currentRow) {
          aiSuggestions.handleFieldBlur(currentRow, field.key);
        }
      }

      // Trigger inline AI validation for name/description fields
      // This validates spelling, grammar, and naming conventions using Groq
      // Only trigger if value actually changed to avoid unnecessary API calls
      const trimmedValue = valueToSave ? String(valueToSave).trim() : '';
      const meetsMinLength = field.key === 'description' ? trimmedValue.length >= 10 : trimmedValue.length > 0;
      if (isInlineAiField && valueChanged && meetsMinLength) {
        const currentRow = useValidationStore.getState().rows[rowIndex];
        const fields = useValidationStore.getState().fields;
        if (currentRow) {
          const { setInlineAiValidating, setInlineAiSuggestion, markInlineAiAutoValidated, inlineAi } = useValidationStore.getState();
          const fieldKey = field.key as 'name' | 'description';
          const validationKey = `${productIndex}-${fieldKey}`;

          // Skip if validation is already in progress (auto-validation may have started)
          if (inlineAi.validating.has(validationKey)) {
            console.log(`[InlineAI] Skipping ${fieldKey} blur validation - already in progress`);
            return;
          }

          // Skip if accepting an AI suggestion (value matches current suggestion)
          // This prevents re-validating when user clicks "Accept" on a suggestion
          const currentSuggestion = inlineAi.suggestions.get(productIndex)?.[fieldKey];
          if (currentSuggestion?.suggestion && currentSuggestion.suggestion === String(valueToSave)) {
            console.log(`[InlineAI] Skipping ${fieldKey} blur validation - accepting AI suggestion`);
            return;
          }

          // Mark as validating and auto-validated (prevents race with auto-validation hook)
          setInlineAiValidating(validationKey, true);
          markInlineAiAutoValidated(productIndex, fieldKey);

          // Build payload using centralized utility
          const rows = useValidationStore.getState().rows;
          const productPayload = fieldKey === 'name'
            ? buildNameValidationPayload(currentRow, fields, rows, { name: String(valueToSave) })
            : buildDescriptionValidationPayload(currentRow, fields, { description: String(valueToSave) });

          // Call the appropriate API endpoint
          const endpoint = fieldKey === 'name'
            ? '/api/ai/validate/inline/name'
            : '/api/ai/validate/inline/description';

          apiFetch(endpoint, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ product: productPayload }),
          })
            .then(res => res.json())
            .then(result => {
              if (result.success !== false) {
                setInlineAiSuggestion(productIndex, fieldKey, {
                  isValid: result.isValid ?? true,
                  suggestion: result.suggestion,
                  issues: result.issues || [],
                  latencyMs: result.latencyMs,
                });
              }
            })
            .catch(err => {
              console.error(`[InlineAI] ${fieldKey} validation error:`, err);
            })
            .finally(() => {
              setInlineAiValidating(`${productIndex}-${fieldKey}`, false);
            });
        }
      }
    }, 0);
  }, [rowIndex, field.key, isEmbeddingField, aiSuggestions, isInlineAiField, productIndex]);

  // Manual re-validate: triggers inline AI validation regardless of value changes
  const handleRevalidate = useCallback(() => {
    if (!isInlineAiField) return;
    const state = useValidationStore.getState();
    const currentRow = state.rows[rowIndex];
    if (!currentRow) return;

    const fieldKey = field.key as 'name' | 'description';
    const currentValue = String(currentRow[fieldKey] ?? '').trim();

    // Name requires non-empty, description requires ≥10 chars
    if (fieldKey === 'name' && !currentValue) return;
    if (fieldKey === 'description' && currentValue.length < 10) return;

    const validationKey = `${productIndex}-${fieldKey}`;
    if (state.inlineAi.validating.has(validationKey)) return;

    const { setInlineAiValidating, setInlineAiSuggestion, markInlineAiAutoValidated, fields: storeFields, rows } = state;
    setInlineAiValidating(validationKey, true);
    markInlineAiAutoValidated(productIndex, fieldKey);

    // Clear dismissed state so new result shows
    const suggestions = state.inlineAi.suggestions.get(productIndex);
    if (suggestions?.dismissed?.[fieldKey]) {
      // Reset dismissed by re-setting suggestion (will be overwritten by API result)
      setInlineAiSuggestion(productIndex, fieldKey, {
        isValid: true,
        suggestion: undefined,
        issues: [],
      });
    }

    const payload = fieldKey === 'name'
      ? buildNameValidationPayload(currentRow, storeFields, rows)
      : buildDescriptionValidationPayload(currentRow, storeFields);

    const endpoint = fieldKey === 'name'
      ? '/api/ai/validate/inline/name'
      : '/api/ai/validate/inline/description';

    apiFetch(endpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ product: payload }),
    })
      .then(res => res.json())
      .then(result => {
        if (result.success !== false) {
          setInlineAiSuggestion(productIndex, fieldKey, {
            isValid: result.isValid ?? true,
            suggestion: result.suggestion,
            issues: result.issues || [],
            latencyMs: result.latencyMs,
          });
        }
      })
      .catch(err => console.error(`[InlineAI] manual ${fieldKey} revalidation error:`, err))
      .finally(() => setInlineAiValidating(validationKey, false));
  }, [rowIndex, field.key, isInlineAiField, productIndex]);

  // Stable callback for fetching options (for line/subline dropdowns)
  const handleFetchOptions = useCallback(async () => {
    const state = useValidationStore.getState();

    if (needsCompany && company) {
      const companyId = String(company);
      const cached = state.productLinesCache.get(companyId);
      if (cached) return cached;

      state.setLoadingProductLines(companyId, true);
      try {
        const response = await apiFetch(`/api/import/product-lines/${companyId}`);
        const lines = await response.json();
        const opts = lines.map((ln: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
          label: ln.name || ln.label || String(ln.value || ln.id),
          value: String(ln.value || ln.id),
        }));
        state.setProductLines(companyId, opts);
        return opts;
      } catch (error) {
        console.error('Error fetching product lines:', error);
        return [];
      } finally {
        state.setLoadingProductLines(companyId, false);
      }
    }

    if (needsLine && line) {
      const lineId = String(line);
      const cached = state.sublinesCache.get(lineId);
      if (cached) return cached;

      state.setLoadingSublines(lineId, true);
      try {
        const response = await apiFetch(`/api/import/sublines/${lineId}`);
        const sublines = await response.json();
        const opts = sublines.map((subline: { name?: string; label?: string; value?: string | number; id?: string | number }) => ({
          label: subline.name || subline.label || String(subline.value || subline.id),
          value: String(subline.value || subline.id),
        }));
        state.setSublines(lineId, opts);
        return opts;
      } catch (error) {
        console.error('Error fetching sublines:', error);
        return [];
      } finally {
        state.setLoadingSublines(lineId, false);
      }
    }

    return [];
  }, [needsCompany, needsLine, company, line]);

  // Determine cell highlighting classes
  const cellHighlightClass = cn(
    'relative w-full group',
    isCopyDownSource && 'ring-2 ring-blue-500 ring-inset rounded',
    isInCopyDownRange && 'bg-blue-100 dark:bg-blue-900/30',
    isCopyDownTarget && !isInCopyDownRange && 'hover:bg-blue-50 dark:hover:bg-blue-900/20 cursor-pointer'
  );

  // When in copy-down mode for this field, make cell non-interactive so clicks go to parent
  const isCopyDownModeForThisField = isCopyDownActive && (isCopyDownSource || isCopyDownTarget);

  // Style override to make cell components transparent when copy-down highlighting should show
  const cellWrapperStyle = (isCopyDownSource || isInCopyDownRange) ? {
    '--cell-bg': 'transparent',
  } as React.CSSProperties : undefined;

  return (
    <div
      className={cellHighlightClass}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
      onClick={isCopyDownTarget ? handleTargetClick : undefined}
      style={cellWrapperStyle}
    >
      {/* Wrap cell in a div that blocks pointer events during copy-down for this field */}
      <div className={cn(
        isCopyDownModeForThisField && 'pointer-events-none',
        // Force transparent background on child buttons/inputs when copy-down is active
        (isCopyDownSource || isInCopyDownRange) && '[&_button]:bg-transparent [&_input]:bg-transparent'
      )}>
        <CellComponent
          value={value}
          field={field}
          options={options}
          rowIndex={rowIndex}
          productIndex={productIndex}
          isValidating={isValidating}
          errors={errors}
          onChange={handleChange}
          onBlur={handleBlur}
          onFetchOptions={needsCompany || needsLine ? handleFetchOptions : undefined}
          isLoadingOptions={isLoadingOptions}
          // Pass AI suggestion props for description field (MultilineInput handles it internally)
          // Only pass aiSuggestion when showSuggestion is true (respects dismissed state)
          {...(field.key === 'description' && {
            aiSuggestion: showSuggestion ? fieldSuggestion : undefined,
            isAiValidating: isInlineAiValidating,
            onDismissAiSuggestion: () => {
              useValidationStore.getState().dismissInlineAiSuggestion(productIndex, 'description');
            },
            onRevalidate: handleRevalidate,
          })}
        />
      </div>

      {/* Copy-down button - appears on hover, positioned to avoid error icons */}
      {showCopyDownButton && (
      <TooltipProvider>
          <Tooltip>
            <TooltipTrigger asChild>
              <button
                type="button"
                onClick={handleStartCopyDown}
                className={cn(
                  'absolute top-1/2 -translate-y-1/2 z-10 p-1 rounded-full',
                  'bg-blue-50 hover:bg-blue-100 text-blue-500 hover:text-blue-600',
                  'dark:bg-blue-900/50 dark:hover:bg-blue-900 dark:text-blue-400',
                  'shadow-sm',
                  // Position further left if there are errors to avoid overlap
                  hasErrors ? 'right-7' : 'right-0.5'
                )}
              >
                <ArrowDown className="h-3.5 w-3.5" />
              </button>
            </TooltipTrigger>
            <TooltipContent side="top">
              Copy value to rows below
            </TooltipContent>
          </Tooltip>
        </TooltipProvider>
      )}

      {/* UPC Generate button - appears on hover for empty UPC cells */}
      {showGenerateUpcButton && (
        <TooltipProvider>
          <Tooltip>
            <TooltipTrigger asChild>
              <button
                type="button"
                onClick={handleGenerateUpc}
                disabled={!hasValidSupplier}
                className={cn(
                  'absolute right-1 top-1/2 -translate-y-1/2 z-10 flex items-center gap-1',
                  'rounded-md border border-input bg-background px-2 py-1 text-xs shadow-sm',
                  'opacity-0 group-hover:opacity-100 transition-opacity',
                  hasValidSupplier
                    ? 'text-muted-foreground hover:bg-accent hover:text-accent-foreground'
                    : 'text-muted-foreground/50 cursor-not-allowed'
                )}
              >
                <Wand2 className="h-3 w-3" />
              </button>
            </TooltipTrigger>
            <TooltipContent side="top">
              <p>{hasValidSupplier ? 'Generate UPC' : 'Select a supplier first'}</p>
            </TooltipContent>
          </Tooltip>
        </TooltipProvider>
      )}

      {/* UPC generating spinner */}
      {isGeneratingUpc && (
        <div className="absolute right-1 top-1/2 -translate-y-1/2 z-10">
          <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
        </div>
      )}

      {/* Inline AI validation spinner - only for name field (description handles it internally) */}
      {isInlineAiValidating && field.key === 'name' && (
        <div className="absolute right-1 top-1/2 -translate-y-1/2 z-10">
          <Loader2 className="h-4 w-4 animate-spin text-purple-500" />
        </div>
      )}

      {/* AI Suggestion badge - only for name field (description handles it inside its popover) */}
      {showSuggestion && fieldSuggestion && field.key === 'name' && (
        <div className="absolute top-full left-0 right-0 z-20 mt-1">
          <AiSuggestionBadge
            suggestion={fieldSuggestion.suggestion!}
            issues={fieldSuggestion.issues}
            onAccept={(editedValue) => {
              const state = useValidationStore.getState();
              // Update the cell with the (possibly edited) value
              state.updateCell(rowIndex, 'name', editedValue);
              // Dismiss the suggestion
              state.dismissInlineAiSuggestion(productIndex, 'name');
            }}
            onDismiss={() => {
              useValidationStore.getState().dismissInlineAiSuggestion(productIndex, 'name');
            }}
            onRevalidate={handleRevalidate}
            isRevalidating={isInlineAiValidating}
            compact
          />
        </div>
      )}
    </div>
  );
});

CellWrapper.displayName = 'CellWrapper';

/**
 * Template column width
 */
const TEMPLATE_COLUMN_WIDTH = 200;

/** Maximum number of columns that can be pinned simultaneously */
const MAX_PINNED_COLUMNS = 3;

/**
 * TemplateCell Component
 *
 * Per-row template dropdown that subscribes to templates from store.
 * PERFORMANCE: Templates rarely change, so this subscription is acceptable.
 */
interface TemplateCellProps {
  rowIndex: number;
  currentTemplateId: string | undefined;
  defaultBrand: string | undefined;
}

const TemplateCell = memo(({ rowIndex, currentTemplateId, defaultBrand }: TemplateCellProps) => {
  // Subscribe to templates - these rarely change
  const templates = useValidationStore((state) => state.templates);
  const templatesLoading = useValidationStore((state) => state.templatesLoading);
  const fields = useValidationStore((state) => state.fields);

  // Get company name lookup from field options
  const companyLookup = useMemo(() => {
    const companyField = fields.find((f) => f.key === 'company');
    if (!companyField || companyField.fieldType.type !== 'select') return new Map<string, string>();
    const opts = companyField.fieldType.options || [];
    return new Map(opts.map((opt) => [opt.value, opt.label]));
  }, [fields]);

  // Helper to get display text - looks up company name from field options
  const getTemplateDisplayText = useCallback(
    (templateId: string | null): string => {
      if (!templateId) return '';
      const template = templates.find((t) => t.id.toString() === templateId);
      if (!template) return '';
      const companyName = companyLookup.get(template.company) || template.company || 'Unknown';
      return `${companyName} - ${template.product_type || 'Unknown'}`;
    },
    [templates, companyLookup]
  );

  // Handle template selection - apply template to this row
  const handleTemplateChange = useCallback(
    async (templateId: string) => {
      if (!templateId) return;

      const template = templates.find((t) => t.id.toString() === templateId);
      if (!template) return;

      const state = useValidationStore.getState();
      const { updateRow, setError, clearFieldError, setRowValidationStatus,
              setUpcStatus, setGeneratedItemNumber, cacheUpcResult, getCachedItemNumber,
              startValidatingCell, stopValidatingCell, updateCell } = state;

      // Get current row data before applying template
      const currentRow = state.rows[rowIndex];

      // Build updates from template
      const updates: Partial<RowData> = {
        __template: templateId,
      };

      // Copy template fields to row (excluding metadata)
      const excludeFields = ['id', '__index', '__meta', '__template', '__original', '__corrected', '__changes', '__aiSupplemental'];
      const templateFieldsSet = new Set<string>();
      Object.entries(template).forEach(([key, value]) => {
        if (!excludeFields.includes(key)) {
          updates[key] = value;
          templateFieldsSet.add(key);
        }
      });

      // If template sets company but not line, clear the line value
      // (old line won't be valid for new company)
      if (templateFieldsSet.has('company') && !templateFieldsSet.has('line')) {
        updates.line = '';
      }

      // If template sets line but not subline, clear the subline value
      if (templateFieldsSet.has('line') && !templateFieldsSet.has('subline')) {
        updates.subline = '';
      }

      // Apply updates
      updateRow(rowIndex, updates);

      // Clear errors only for fields that were set by template
      templateFieldsSet.forEach((fieldKey) => {
        clearFieldError(rowIndex, fieldKey);
      });

      // Also clear line/subline errors if they were cleared
      if (templateFieldsSet.has('company') && !templateFieldsSet.has('line')) {
        clearFieldError(rowIndex, 'line');
      }
      if (templateFieldsSet.has('line') && !templateFieldsSet.has('subline')) {
        clearFieldError(rowIndex, 'subline');
      }

      toast.success('Template applied');

      // Trigger inline AI validation for name/description if template set those fields
      const productIndex = currentRow?.__index;
      if (productIndex) {
        const { setInlineAiValidating, setInlineAiSuggestion, rows } = state;

        // Get the updated row data (after template applied)
        const updatedRow = { ...currentRow, ...updates } as RowData;

        // Trigger name validation if template set name
        if (templateFieldsSet.has('name') && updates.name && String(updates.name).trim()) {
          setInlineAiValidating(`${productIndex}-name`, true);

          const payload = buildNameValidationPayload(updatedRow, fields, rows);

          apiFetch('/api/ai/validate/inline/name', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ product: payload }),
          })
            .then(res => res.json())
            .then(result => {
              if (result.success !== false) {
                setInlineAiSuggestion(productIndex, 'name', {
                  isValid: result.isValid ?? true,
                  suggestion: result.suggestion,
                  issues: result.issues || [],
                  latencyMs: result.latencyMs,
                });
              }
            })
            .catch(err => console.error('[InlineAI] name validation error:', err))
            .finally(() => setInlineAiValidating(`${productIndex}-name`, false));
        }

        // Trigger description validation if template set description
        if (templateFieldsSet.has('description') && updates.description && String(updates.description).trim()) {
          setInlineAiValidating(`${productIndex}-description`, true);

          const payload = buildDescriptionValidationPayload(updatedRow, fields);

          apiFetch('/api/ai/validate/inline/description', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ product: payload }),
          })
            .then(res => res.json())
            .then(result => {
              if (result.success !== false) {
                setInlineAiSuggestion(productIndex, 'description', {
                  isValid: result.isValid ?? true,
                  suggestion: result.suggestion,
                  issues: result.issues || [],
                  latencyMs: result.latencyMs,
                });
              }
            })
            .catch(err => console.error('[InlineAI] description validation error:', err))
            .finally(() => setInlineAiValidating(`${productIndex}-description`, false));
        }
      }

      // Trigger UPC validation if template set supplier or upc, and we have both values
      const finalSupplier = updates.supplier ?? currentRow?.supplier;
      const finalUpc = updates.upc ?? currentRow?.upc;

      if (finalSupplier && finalUpc) {
        const supplierId = String(finalSupplier);
        const upc = String(finalUpc);

        // Check cache first
        const cached = getCachedItemNumber(supplierId, upc);
        if (cached) {
          setGeneratedItemNumber(rowIndex, cached);
          setRowValidationStatus(rowIndex, 'validated');
          return;
        }

        // Start validation
        setUpcStatus(rowIndex, 'validating');
        startValidatingCell(rowIndex, 'item_number');

        try {
          const response = await apiFetch(
            `${config.apiUrl}/import/check-upc-and-generate-sku?upc=${encodeURIComponent(upc)}&supplierId=${encodeURIComponent(supplierId)}`
          );

          const payload = await response.json().catch(() => null);

          if (response.status === 409) {
            // UPC already exists
            setError(rowIndex, 'upc', {
              message: 'A product with this UPC already exists',
              level: 'error',
              source: ErrorSource.Upc,
              type: ErrorType.Unique,
            });
            setUpcStatus(rowIndex, 'error');
            updateCell(rowIndex, 'item_number', '');
            setRowValidationStatus(rowIndex, 'error');
          } else if (response.ok && payload?.success && payload?.itemNumber) {
            // Success - cache and apply
            cacheUpcResult(supplierId, upc, payload.itemNumber);
            setGeneratedItemNumber(rowIndex, payload.itemNumber);
            clearFieldError(rowIndex, 'upc');
            setUpcStatus(rowIndex, 'done');
            setRowValidationStatus(rowIndex, 'validated');
          } else {
            setUpcStatus(rowIndex, 'error');
            updateCell(rowIndex, 'item_number', '');
          }
        } catch (error) {
          console.error('UPC validation error:', error);
          setUpcStatus(rowIndex, 'error');
        } finally {
          stopValidatingCell(rowIndex, 'item_number');
        }
      }
    },
    [templates, rowIndex]
  );

  if (templatesLoading) {
    return (
      <div className="w-full h-8 flex items-center justify-center">
        <Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
      </div>
    );
  }

  if (!templates || templates.length === 0) {
    return (
      <div className="w-full h-8 flex items-center justify-center text-xs text-muted-foreground">
        No templates
      </div>
    );
  }

  return (
    <SearchableTemplateSelect
      templates={templates}
      value={currentTemplateId || ''}
      onValueChange={handleTemplateChange}
      getTemplateDisplayText={getTemplateDisplayText}
      placeholder="Select template"
      defaultBrand={defaultBrand}
      triggerClassName="h-8 text-xs"
    />
  );
});

TemplateCell.displayName = 'TemplateCell';

/**
 * Row wrapper that subscribes to ALL row-level state
 *
 * PERFORMANCE: This is now the ONLY component that subscribes to store state
 * for a given row. It passes all data down to CellWrapper as props.
 * This reduces subscriptions from ~2100 (7 per cell) to ~150 (5 per row).
 */
/** Layout info for a single pinned column */
type PinnedColumnEntry = {
  left: number;        // Cumulative left offset for sticky-left positioning
  right: number;       // Cumulative right offset for sticky-right positioning
  naturalLeft: number; // Natural left position in the full-width row (for stuck detection)
  width: number;
  zIndex: number;
};

interface VirtualRowProps {
  rowIndex: number;
  rowId: string;
  virtualStart: number;
  columns: ColumnDef<RowData>[];
  fields: Field<string>[];
  totalRowCount: number;
  /** Map of field key → sticky layout for all currently pinned columns */
  pinnedColumnLayout: Map<string, PinnedColumnEntry>;
  /** Per-column stick direction: only columns that are actually stuck appear in this map */
  stickyStates: Map<string, 'left' | 'right'>;
  /** Field key of the left-shadow column (rightmost column stuck left) */
  shadowLeftKey: string | null;
  /** Field key of the right-shadow column (leftmost column stuck right) */
  shadowRightKey: string | null;
}

const VirtualRow = memo(({
  rowIndex,
  rowId,
  virtualStart,
  columns,
  fields,
  totalRowCount,
  pinnedColumnLayout,
  stickyStates,
  shadowLeftKey,
  shadowRightKey,
}: VirtualRowProps) => {
  // Subscribe to row data - this is THE subscription for all cell values in this row
  const rowData = useValidationStore(
    useCallback((state) => state.rows[rowIndex], [rowIndex])
  );

  // Subscribe to errors for this row - MUST return stable reference!
  const rowErrors = useValidationStore(
    useCallback((state) => state.errors.get(rowIndex) ?? EMPTY_ROW_ERRORS, [rowIndex])
  );

  // DON'T subscribe to validatingCells for most fields - check it during render instead
  // This avoids creating new objects in selectors which causes infinite loops
  // EXCEPTION: Subscribe specifically for item_number so it shows loading state during UPC validation
  const isItemNumberValidating = useValidationStore(
    useCallback((state) => state.validatingCells.has(`${rowIndex}-item_number`), [rowIndex])
  );

  // Subscribe to loading states for line/subline fields
  // These need reactive updates so loading spinners clear when API calls complete
  const company = rowData?.company;
  const line = rowData?.line;

  const isLoadingProductLinesForCompany = useValidationStore(
    useCallback(
      (state) => (company ? state.loadingProductLines.has(String(company)) : false),
      [company]
    )
  );

  const isLoadingSublineForLine = useValidationStore(
    useCallback(
      (state) => (line ? state.loadingSublines.has(String(line)) : false),
      [line]
    )
  );

  // Subscribe to selection status
  const isSelected = useValidationStore(
    useCallback((state) => state.selectedRows.has(rowId), [rowId])
  );

  // Subscribe to copy-down mode - returns primitives for performance
  const copyDownMode = useValidationStore(
    useCallback((state) => state.copyDownMode, [])
  );

  // Subscribe to inline AI suggestions for this row (for name/description validation)
  const inlineAiSuggestion = useValidationStore(
    useCallback((state) => {
      const suggestion = state.inlineAi.suggestions.get(rowId);
      // Debug: Log when subscription returns a value
      if (suggestion) {
        console.log('[VirtualRow] Got suggestion for rowId:', rowId, suggestion);
      }
      return suggestion;
    }, [rowId])
  );

  // Check if inline AI validation is running for this row
  const isInlineAiValidatingName = useValidationStore(
    useCallback((state) => state.inlineAi.validating.has(`${rowId}-name`), [rowId])
  );
  const isInlineAiValidatingDescription = useValidationStore(
    useCallback((state) => state.inlineAi.validating.has(`${rowId}-description`), [rowId])
  );

  // DON'T subscribe to caches - read via getState() when needed
  // Subscribing to caches causes ALL rows with same company to re-render when cache updates!
  // Note: company and line are already declared above for loading state subscriptions
  const supplier = rowData?.supplier;

  // Get action via getState() - no need to subscribe
  const toggleRowSelection = useValidationStore.getState().toggleRowSelection;

  const hasErrors = Object.keys(rowErrors).length > 0;

  // Check if this row has a visible AI suggestion badge (needs higher z-index to show above next row)
  // Only name field shows a floating badge - description handles AI suggestions inside its popover
  const hasVisibleAiSuggestion = inlineAiSuggestion?.name &&
    !inlineAiSuggestion.name.isValid &&
    inlineAiSuggestion.name.suggestion &&
    !inlineAiSuggestion.dismissed?.name;

  // Handle mouse enter for copy-down target selection
  const handleMouseEnter = useCallback(() => {
    if (copyDownMode.isActive && copyDownMode.sourceRowIndex !== null && rowIndex > copyDownMode.sourceRowIndex) {
      useValidationStore.getState().setTargetRowHover(rowIndex);
    }
  }, [copyDownMode.isActive, copyDownMode.sourceRowIndex, rowIndex]);

  return (
    <div
      data-row-index={rowIndex}
      className={cn(
        'flex absolute',
        // Use box-shadow for bottom border - renders more consistently with transforms than border-b
        'shadow-[inset_0_-1px_0_0_hsl(var(--border))]',
        hasErrors && 'bg-destructive/5',
        isSelected && 'bg-blue-100 dark:bg-blue-900/40'
      )}
      style={{
        height: ROW_HEIGHT,
        // Round to whole pixels to prevent sub-pixel rendering issues during scroll
        transform: `translateY(${Math.round(virtualStart)}px)`,
        // Promote to GPU layer for smoother rendering
        willChange: 'transform',
        // Elevate row when it has a visible AI suggestion so badge shows above next row
        zIndex: hasVisibleAiSuggestion ? 10 : undefined,
      }}
      onMouseEnter={handleMouseEnter}
    >
      {/* Selection checkbox cell */}
      <div
        className="px-2 py-3 flex items-start justify-center shadow-[inset_-1px_0_0_0_hsl(var(--border))]"
        style={{
          width: columns[0]?.size || 40,
          minWidth: columns[0]?.size || 40,
          flexShrink: 0,
        }}
      >
        <Checkbox
          checked={isSelected}
          onCheckedChange={() => toggleRowSelection(rowId)}
        />
      </div>

      {/* Template column */}
      <div
        className="px-2 py-2 flex items-start overflow-hidden shadow-[inset_-1px_0_0_0_hsl(var(--border))]"
        style={{
          width: TEMPLATE_COLUMN_WIDTH,
          minWidth: TEMPLATE_COLUMN_WIDTH,
          flexShrink: 0,
        }}
      >
        <TemplateCell
          rowIndex={rowIndex}
          currentTemplateId={rowData?.__template}
          defaultBrand={typeof company === 'string' ? company : undefined}
        />
      </div>

      {/* Data cells - pass all data as props */}
      {fields.map((field, fieldIndex) => {
        // Account for selection (0) and template (1) columns
        const columnWidth = columns[fieldIndex + 2]?.size || field.width || 150;
        const fieldErrors = rowErrors[field.key] || EMPTY_ERRORS;
        // Check validating status via getState() - not subscribed to avoid object creation
        // EXCEPTION: item_number uses the subscribed isItemNumberValidating for reactive loading state
        const isValidating = field.key === 'item_number'
          ? isItemNumberValidating
          : useValidationStore.getState().validatingCells.has(`${rowIndex}-${field.key}`);

        // CRITICAL: Only pass company/line to cells that need them!
        // Passing to all cells breaks memoization - when company changes, ALL cells re-render
        const needsCompany = field.key === 'line';
        const needsLine = field.key === 'subline';
        const needsSupplier = field.key === 'upc';

        // Check loading state for dependent dropdowns - uses subscribed values for reactivity
        let isLoadingOptions = false;
        if (needsCompany) {
          isLoadingOptions = isLoadingProductLinesForCompany;
        } else if (needsLine) {
          isLoadingOptions = isLoadingSublineForLine;
        }

        // Calculate copy-down state for this cell
        const isCopyDownActive = copyDownMode.isActive;
        const isCopyDownSource = isCopyDownActive &&
          copyDownMode.sourceRowIndex === rowIndex &&
          copyDownMode.sourceFieldKey === field.key;
        const isCopyDownTarget = isCopyDownActive &&
          copyDownMode.sourceFieldKey === field.key &&
          copyDownMode.sourceRowIndex !== null &&
          rowIndex > copyDownMode.sourceRowIndex;
        const isInCopyDownRange = isCopyDownTarget &&
          copyDownMode.targetRowIndex !== null &&
          rowIndex <= copyDownMode.targetRowIndex;

        const isNameColumn = field.key === 'name';

        // Determine sticky behavior per column (guard: pinnedEntry must exist — stickyStates can lag behind unpins)
        const pinnedEntry = pinnedColumnLayout.get(field.key);
        const columnStickDir = pinnedEntry ? stickyStates.get(field.key) : undefined;
        const shouldBeSticky = !!columnStickDir;
        const stickyLeft = columnStickDir === 'left';
        const stickyRight = columnStickDir === 'right';
        // Shadow on the outermost actually-stuck column per side
        const isShadowColumn = field.key === shadowLeftKey || field.key === shadowRightKey;

        return (
          <div
            key={field.key}
            data-cell-field={field.key}
            className={cn(
              "px-2 py-2 flex items-start",
              // Use box-shadow for right border - renders more consistently with transforms
              // last:shadow-none removes the shadow from the last cell
              "shadow-[inset_-1px_0_0_0_hsl(var(--border))] last:shadow-none",
              // overflow-visible needed for: name column (AI suggestion badge) and shadow columns (drop shadow)
              (isNameColumn || isShadowColumn) ? "overflow-visible" : "overflow-hidden",
              // Pinned column sticky behavior - only when enabled and scrolled appropriately
              shouldBeSticky && "lg:sticky",
              // Drop shadow only on the outermost actually-stuck column
              isShadowColumn && stickyLeft && "lg:shadow-[inset_-1px_0_0_0_hsl(var(--border)),2px_0_4px_-2px_rgba(0,0,0,0.15)]",
              isShadowColumn && stickyRight && "lg:shadow-[inset_-1px_0_0_0_hsl(var(--border)),inset_1px_0_0_0_hsl(var(--border)),-2px_0_4px_-2px_rgba(0,0,0,0.15)]",
              // Solid background when sticky to overlay content
              // Use explicit [background:] syntax for consistent specificity
              // Selection (blue) takes priority over errors (red)
              shouldBeSticky && (
                isSelected
                  ? "lg:[background:#dbeafe] lg:dark:[background:hsl(221_83%_25%/0.4)]"
                  : hasErrors
                    ? "lg:[background:linear-gradient(hsl(var(--destructive)/0.05),hsl(var(--destructive)/0.05)),hsl(var(--background))]"
                    : "lg:[background:hsl(var(--background))]"
              ),
              // Make child inputs/buttons transparent when sticky + selected so blue background shows through
              shouldBeSticky && isSelected && "lg:[&_input]:!bg-transparent lg:[&_button]:!bg-transparent lg:[&_textarea]:!bg-transparent"
            )}
            style={{
              width: columnWidth,
              minWidth: columnWidth,
              flexShrink: 0,
              // Z-index layered per pinned column so rightmost renders on top
              ...(shouldBeSticky && { zIndex: pinnedEntry!.zIndex }),
              // Position sticky left or right based on scroll direction
              ...(stickyLeft && { left: pinnedEntry!.left }),
              ...(stickyRight && { right: pinnedEntry!.right }),
            }}
          >
            <CellWrapper
              field={field}
              rowIndex={rowIndex}
              productIndex={rowId}
              value={rowData?.[field.key]}
              errors={fieldErrors}
              isValidating={isValidating}
              company={needsCompany ? company : undefined}
              line={needsLine ? line : undefined}
              supplier={needsSupplier ? supplier : undefined}
              isLoadingOptions={isLoadingOptions}
              isCopyDownActive={isCopyDownActive}
              isCopyDownSource={isCopyDownSource}
              isInCopyDownRange={isInCopyDownRange}
              isCopyDownTarget={isCopyDownTarget}
              totalRowCount={totalRowCount}
              inlineAiSuggestion={inlineAiSuggestion}
              isInlineAiValidating={
                field.key === 'name' ? isInlineAiValidatingName :
                field.key === 'description' ? isInlineAiValidatingDescription : false
              }
            />
          </div>
        );
      })}
    </div>
  );
});

VirtualRow.displayName = 'VirtualRow';

/**
 * Header checkbox component - isolated to prevent re-renders of the entire table
 * When filtering is active, only selects/deselects visible (filtered) rows
 */
const HeaderCheckbox = memo(() => {
  const filters = useFilters();
  // Subscribe to row count to recalculate when rows are added/removed
  const rowCount = useRowCount();

  // Compute which rows are visible based on current filters
  const { visibleRowIds, visibleCount } = useMemo(() => {
    const { rows, errors } = useValidationStore.getState();
    const isFiltering = filters.searchText || filters.showErrorsOnly;

    if (!isFiltering) {
      // No filtering - all rows are visible
      const ids = new Set(rows.map((row) => row.__index));
      return { visibleRowIds: ids, visibleCount: rows.length };
    }

    // Apply filters to get visible row IDs
    const ids = new Set<string>();
    rows.forEach((row, index) => {
      // Apply search filter
      if (filters.searchText) {
        const searchLower = filters.searchText.toLowerCase();
        const matches = Object.values(row).some((value) =>
          String(value ?? '').toLowerCase().includes(searchLower)
        );
        if (!matches) return;
      }

      // Apply errors-only filter
      if (filters.showErrorsOnly) {
        const rowErrors = errors.get(index);
        if (!rowErrors || Object.keys(rowErrors).length === 0) return;
      }

      ids.add(row.__index);
    });

    return { visibleRowIds: ids, visibleCount: ids.size };
  }, [filters.searchText, filters.showErrorsOnly, rowCount]);

  // Check selection state against visible rows only
  const selectedRows = useValidationStore((state) => state.selectedRows);
  const selectedVisibleCount = useMemo(() => {
    let count = 0;
    visibleRowIds.forEach((id) => {
      if (selectedRows.has(id)) count++;
    });
    return count;
  }, [visibleRowIds, selectedRows]);

  const allVisibleSelected = visibleCount > 0 && selectedVisibleCount === visibleCount;
  const someVisibleSelected = selectedVisibleCount > 0 && selectedVisibleCount < visibleCount;

  const handleChange = useCallback((value: boolean | 'indeterminate') => {
    const { setSelectedRows, selectedRows: currentSelected } = useValidationStore.getState();
    if (value) {
      // Add all visible rows to selection (keep existing selections of non-visible rows)
      const newSelection = new Set(currentSelected);
      visibleRowIds.forEach((id) => newSelection.add(id));
      setSelectedRows(newSelection);
    } else {
      // Remove all visible rows from selection (keep selections of non-visible rows)
      const newSelection = new Set(currentSelected);
      visibleRowIds.forEach((id) => newSelection.delete(id));
      setSelectedRows(newSelection);
    }
  }, [visibleRowIds]);

  return (
    <Checkbox
      checked={allVisibleSelected || (someVisibleSelected && 'indeterminate')}
      onCheckedChange={handleChange}
    />
  );
});

HeaderCheckbox.displayName = 'HeaderCheckbox';

/**
 * PriceColumnHeader Component
 *
 * Renders a column header for MSRP or Cost Each with a hover button
 * that fills empty cells based on the other price field.
 * - MSRP: Opens popover with multiplier options (2x-2.5x) and round-to-.X9 checkbox
 * - Cost Each: Fill with MSRP ÷ 2
 *
 * PERFORMANCE: Uses local hover state and getState() for bulk updates.
 * No store subscriptions - this is purely a UI component that triggers actions.
 */
interface PriceColumnHeaderProps {
  fieldKey: 'msrp' | 'cost_each';
  label: string;
  isRequired: boolean;
  pinButton?: React.ReactNode;
  isPinned?: boolean;
}

const MSRP_MULTIPLIERS = [2.0, 2.1, 2.2, 2.3, 2.4, 2.5];

/**
 * Round up to nearest .X9 (e.g., 12.32 → 12.39, 15.71 → 15.79)
 */
const roundToNine = (value: number): number => {
  const wholePart = Math.floor(value);
  const decimal = value - wholePart;
  // Get the tenths digit and add .09 to make it end in 9
  const tenths = Math.floor(decimal * 10);
  return wholePart + (tenths / 10) + 0.09;
};

const PriceColumnHeader = memo(({ fieldKey, label, isRequired, pinButton }: PriceColumnHeaderProps) => {
  const [isHovered, setIsHovered] = useState(false);
  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
  const [hasFillableCells, setHasFillableCells] = useState(false);
  const [selectedMultiplier, setSelectedMultiplier] = useState(2.0);
  const [shouldRoundToNine, setShouldRoundToNine] = useState(false);

  const isMsrp = fieldKey === 'msrp';

  // Cost Each offers a per-row "divide by Min Qty" action that operates on the current
  // row selection only — subscribe to the selection count to enable/disable it.
  const selectedCount = useValidationStore((state) => (isMsrp ? 0 : state.selectedRows.size));

  // Determine the source field
  const sourceField = isMsrp ? 'cost_each' : 'msrp';
  const tooltipText = isMsrp
    ? 'Fill empty MSRP from Cost'
    : 'Fill empty cells with MSRP ÷ 2';

  // Check if there are any cells that can be filled (called on hover)
  // Now returns true if ANY row has a valid source value (allows overwriting existing values)
  const checkFillableCells = useCallback(() => {
    const { rows } = useValidationStore.getState();
    return rows.some((row) => {
      const sourceValue = row[sourceField];
      const hasSource = sourceValue !== undefined && sourceValue !== null && sourceValue !== '';
      if (hasSource) {
        const sourceNum = parseFloat(String(sourceValue));
        return !isNaN(sourceNum) && sourceNum > 0;
      }
      return false;
    });
  }, [sourceField]);

  // Update fillable check on hover
  const handleMouseEnter = useCallback(() => {
    setIsHovered(true);
    setHasFillableCells(checkFillableCells());
  }, [checkFillableCells]);

  const handleCalculateMsrp = useCallback((multiplier: number, roundNine: boolean) => {
    const updatedIndices: number[] = [];

    // Use setState() for efficient batch update with Immer
    useValidationStore.setState((draft) => {
      draft.rows.forEach((row, index) => {
        const sourceValue = row[sourceField];

        // Fill if source has a value (overwrite existing values based on source)
        const hasSource = sourceValue !== undefined && sourceValue !== null && sourceValue !== '';

        if (hasSource) {
          const sourceNum = parseFloat(String(sourceValue));
          if (!isNaN(sourceNum) && sourceNum > 0) {
            let msrp = sourceNum * multiplier;

            if (multiplier === 2.0) {
              // For 2x: auto-adjust by ±1 cent ONLY if result ends in .99
              const cents = Math.round((msrp % 1) * 100);
              if (cents === 0 || cents === 98) {
                const adjustment = cents === 0 ? -0.01 : 0.01;
                const adjusted = (msrp + adjustment).toFixed(2);
                // Only apply if the adjusted value actually ends in .99
                if (adjusted.endsWith('.99')) {
                  msrp = parseFloat(adjusted);
                }
              }
              // Otherwise leave as-is (exact 2x)
            } else if (roundNine) {
              // For >2x with checkbox: round to nearest .X9
              msrp = roundToNine(msrp);
            }

            draft.rows[index][fieldKey] = msrp.toFixed(2);
            updatedIndices.push(index);
          }
        }
      });
    });

    // Clear validation errors for all updated cells
    if (updatedIndices.length > 0) {
      const { clearFieldError } = useValidationStore.getState();
      updatedIndices.forEach((rowIndex) => {
        clearFieldError(rowIndex, fieldKey);
      });

      toast.success(`Updated ${updatedIndices.length} ${label} value${updatedIndices.length === 1 ? '' : 's'}`);
    }

    setIsPopoverOpen(false);
  }, [fieldKey, sourceField, label]);

  const handleCalculateCostEach = useCallback(() => {
    const updatedIndices: number[] = [];

    useValidationStore.setState((draft) => {
      draft.rows.forEach((row, index) => {
        const sourceValue = row[sourceField];

        // Fill if source has a value (overwrite existing values based on source)
        const hasSource = sourceValue !== undefined && sourceValue !== null && sourceValue !== '';

        if (hasSource) {
          const sourceNum = parseFloat(String(sourceValue));
          if (!isNaN(sourceNum) && sourceNum > 0) {
            draft.rows[index][fieldKey] = (sourceNum / 2).toFixed(2);
            updatedIndices.push(index);
          }
        }
      });
    });

    if (updatedIndices.length > 0) {
      const { clearFieldError } = useValidationStore.getState();
      updatedIndices.forEach((rowIndex) => {
        clearFieldError(rowIndex, fieldKey);
      });

      toast.success(`Updated ${updatedIndices.length} ${label} value${updatedIndices.length === 1 ? '' : 's'}`);
    }
  }, [fieldKey, sourceField, label]);

  /**
   * Divide cost_each by Min Qty (qty_per_unit) for the currently-SELECTED rows only.
   * For cases where only some rows list a pack total but are sold individually.
   * Mirrors the global "divide by min qty" math in dataMutations.ts.
   */
  const handleDivideSelectedByMinQty = useCallback(() => {
    const { selectedRows } = useValidationStore.getState();
    const updatedIndices: number[] = [];

    useValidationStore.setState((draft) => {
      draft.rows.forEach((row, index) => {
        if (!selectedRows.has(row.__index)) return;
        const cost = parseFloat(String(row.cost_each ?? ''));
        const qty = parseInt(String(row.qty_per_unit ?? ''), 10);
        if (!isNaN(cost) && qty > 0) {
          draft.rows[index].cost_each = (cost / qty).toFixed(2);
          updatedIndices.push(index);
        }
      });
    });

    if (updatedIndices.length > 0) {
      const { clearFieldError } = useValidationStore.getState();
      updatedIndices.forEach((rowIndex) => {
        clearFieldError(rowIndex, 'cost_each');
      });
      toast.success(`Divided ${updatedIndices.length} ${label} value${updatedIndices.length === 1 ? '' : 's'} by Min Qty`);
    } else {
      toast.error('No selected rows had both a cost and a Min Qty > 0');
    }

    setIsPopoverOpen(false);
  }, [label]);

  return (
    <div
      className="flex items-center gap-1 min-w-0 w-full group relative"
      onMouseEnter={handleMouseEnter}
      onMouseLeave={() => {
        if (!isPopoverOpen) setIsHovered(false);
      }}
    >
      <span className="">{label}</span>
      {isRequired && (
        <span className="text-destructive flex-shrink-0">*</span>
      )}
      {/* Button group: pin button always visible when pinned, action buttons only on hover */}
      <div className="absolute right-0 top-1/2 -translate-y-1/2 flex items-center gap-1">
        {(isHovered || isPopoverOpen) && (hasFillableCells || (!isMsrp && selectedCount > 0)) && (
          isMsrp ? (
            <Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <PopoverTrigger asChild>
                      <button
                        type="button"
                        onClick={(e) => e.stopPropagation()}
                        className={cn(
                          'flex items-center gap-0.5',
                          'rounded border border-input bg-background p-1 shadow-sm',
                          'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
                        )}
                      >
                        <Calculator className="h-3 w-3" />
                      </button>
                    </PopoverTrigger>
                  </TooltipTrigger>
                  <TooltipContent side="bottom">
                    <p>{tooltipText}</p>
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
              <PopoverContent className="w-52 p-3" align="end">
                <div className="space-y-3">
                  <p className="text-xs font-medium text-muted-foreground">
                    Calculate MSRP from Cost
                  </p>
                  <div>
                    <p className="text-xs text-muted-foreground mb-1.5">Multiplier</p>
                    <div className="grid grid-cols-3 gap-1">
                      {MSRP_MULTIPLIERS.map((m) => (
                        <Button
                          key={m}
                          variant={selectedMultiplier === m ? 'default' : 'outline'}
                          size="sm"
                          className="h-7 text-xs"
                          onClick={() => setSelectedMultiplier(m)}
                        >
                          {m}x
                        </Button>
                      ))}
                    </div>
                  </div>
                  {selectedMultiplier > 2.0 && (
                    <div className="flex items-center gap-2">
                      <Checkbox
                        id="round-to-nine"
                        checked={shouldRoundToNine}
                        onCheckedChange={(checked) => setShouldRoundToNine(checked === true)}
                      />
                      <label htmlFor="round-to-nine" className="text-xs cursor-pointer">
                        Round to .X9 (e.g., 12.39)
                      </label>
                    </div>
                  )}
                  {selectedMultiplier === 2.0 && (
                    <p className="text-xs text-muted-foreground">
                      Auto-adjusts ±1¢ for .99 pricing
                    </p>
                  )}
                  <Button
                    size="sm"
                    className="w-full h-7 text-xs"
                    onClick={() => handleCalculateMsrp(selectedMultiplier, shouldRoundToNine)}
                  >
                    Apply
                  </Button>
                </div>
              </PopoverContent>
            </Popover>
          ) : (
            <Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
              <TooltipProvider>
                <Tooltip>
                  <TooltipTrigger asChild>
                    <PopoverTrigger asChild>
                      <button
                        type="button"
                        onClick={(e) => e.stopPropagation()}
                        className={cn(
                          'flex items-center gap-0.5',
                          'rounded border border-input bg-background p-1 shadow-sm',
                          'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
                        )}
                      >
                        <Calculator className="h-3 w-3" />
                      </button>
                    </PopoverTrigger>
                  </TooltipTrigger>
                  <TooltipContent side="bottom">
                    <p>Calculate Cost Each</p>
                  </TooltipContent>
                </Tooltip>
              </TooltipProvider>
              <PopoverContent className="w-60 p-3" align="end">
                <div className="space-y-3">
                  <p className="text-xs font-medium text-muted-foreground">
                    Calculate Cost Each
                  </p>
                  <Button
                    size="sm"
                    variant="outline"
                    className="w-full h-7 text-xs"
                    disabled={!hasFillableCells}
                    onClick={() => {
                      handleCalculateCostEach();
                      setIsPopoverOpen(false);
                    }}
                  >
                    Fill from MSRP ÷ 2
                  </Button>
                  <div className="border-t pt-3 space-y-1.5">
                    <Button
                      size="sm"
                      className="w-full h-7 text-xs"
                      disabled={selectedCount === 0}
                      onClick={handleDivideSelectedByMinQty}
                    >
                      Divide selected by Min Qty
                    </Button>
                    <p className="text-xs text-muted-foreground">
                      {selectedCount > 0
                        ? `Divides cost ÷ Min Qty for ${selectedCount} selected row${selectedCount === 1 ? '' : 's'}.`
                        : 'Select rows first to divide their cost by Min Qty.'}
                    </p>
                  </div>
                </div>
              </PopoverContent>
            </Popover>
          )
        )}
        {pinButton}
      </div>
    </div>
  );
});

PriceColumnHeader.displayName = 'PriceColumnHeader';

/**
 * UnitConversionColumnHeader Component
 *
 * Renders a column header for weight/dimension fields with a hover button
 * that opens a popover with unit conversion options.
 * - Weight: grams → oz, lbs → oz, kg → oz
 * - Dimensions: cm → in, mm → in
 *
 * PERFORMANCE: Uses local hover state and getState() for bulk updates.
 */
interface UnitConversionColumnHeaderProps {
  fieldKey: 'weight' | 'length' | 'width' | 'height';
  label: string;
  isRequired: boolean;
  pinButton?: React.ReactNode;
  isPinned?: boolean;
}

type ConversionOption = {
  label: string;
  factor: number;
  roundTo?: number;
};

const WEIGHT_CONVERSIONS: ConversionOption[] = [
  { label: 'Grams → Ounces', factor: 0.035274, roundTo: 2 },
  { label: 'Pounds → Ounces', factor: 16, roundTo: 2 },
  { label: 'Kilograms → Ounces', factor: 35.274, roundTo: 2 },
];

const DIMENSION_CONVERSIONS: ConversionOption[] = [
  { label: 'Centimeters → Inches', factor: 0.393701, roundTo: 2 },
  { label: 'Millimeters → Inches', factor: 0.0393701, roundTo: 2 },
];

const UnitConversionColumnHeader = memo(({ fieldKey, label, isRequired, pinButton }: UnitConversionColumnHeaderProps) => {
  const [isHovered, setIsHovered] = useState(false);
  const [isPopoverOpen, setIsPopoverOpen] = useState(false);
  const [hasConvertibleCells, setHasConvertibleCells] = useState(false);

  const isWeightField = fieldKey === 'weight';
  const conversions = isWeightField ? WEIGHT_CONVERSIONS : DIMENSION_CONVERSIONS;

  // Check if there are any cells with numeric values that can be converted
  const checkConvertibleCells = useCallback(() => {
    const { rows } = useValidationStore.getState();
    return rows.some((row) => {
      const value = row[fieldKey];
      if (value !== undefined && value !== null && value !== '') {
        const num = parseFloat(String(value));
        return !isNaN(num) && num > 0;
      }
      return false;
    });
  }, [fieldKey]);

  // Update convertible check on hover
  const handleMouseEnter = useCallback(() => {
    setIsHovered(true);
    setHasConvertibleCells(checkConvertibleCells());
  }, [checkConvertibleCells]);

  const handleConversion = useCallback((conversion: ConversionOption) => {
    const updatedIndices: number[] = [];

    // Use setState() for efficient batch update with Immer
    useValidationStore.setState((draft) => {
      draft.rows.forEach((row, index) => {
        const value = row[fieldKey];

        if (value !== undefined && value !== null && value !== '') {
          const num = parseFloat(String(value));
          if (!isNaN(num) && num > 0) {
            const converted = num * conversion.factor;
            const rounded = conversion.roundTo !== undefined
              ? converted.toFixed(conversion.roundTo)
              : converted.toString();
            draft.rows[index][fieldKey] = rounded;
            updatedIndices.push(index);
          }
        }
      });
    });

    // Clear validation errors for all updated cells
    if (updatedIndices.length > 0) {
      const { clearFieldError } = useValidationStore.getState();
      updatedIndices.forEach((rowIndex) => {
        clearFieldError(rowIndex, fieldKey);
      });

      toast.success(`Converted ${updatedIndices.length} ${label} value${updatedIndices.length === 1 ? '' : 's'}`);
    } else {
      toast.info('No values to convert');
    }

    setIsPopoverOpen(false);
  }, [fieldKey, label]);

  return (
    <div
      className="flex items-center gap-1 min-w-0 w-full group relative"
      onMouseEnter={handleMouseEnter}
      onMouseLeave={() => {
        if (!isPopoverOpen) setIsHovered(false);
      }}
    >
      <span className="">{label}</span>
      {isRequired && (
        <span className="text-destructive flex-shrink-0">*</span>
      )}
      {/* Button group: pin button always visible when pinned, action buttons only on hover */}
      <div className="absolute right-0 top-1/2 -translate-y-1/2 flex items-center gap-1">
        {(isHovered || isPopoverOpen) && hasConvertibleCells && (
          <Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
            <TooltipProvider>
              <Tooltip>
                <TooltipTrigger asChild>
                  <PopoverTrigger asChild>
                    <button
                      type="button"
                      onClick={(e) => {
                        e.stopPropagation();
                      }}
                      className={cn(
                        'flex items-center gap-0.5',
                        'rounded border border-input bg-background p-1 shadow-sm',
                        'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
                      )}
                    >
                      <Scale className="h-3 w-3" />
                    </button>
                  </PopoverTrigger>
                </TooltipTrigger>
                <TooltipContent side="bottom">
                  <p>Convert units for entire column</p>
                </TooltipContent>
              </Tooltip>
            </TooltipProvider>
            <PopoverContent className="w-48 p-2" align="end">
              <div className="space-y-1">
                <p className="text-xs font-medium text-muted-foreground mb-2">
                  Convert {isWeightField ? 'Weight' : 'Dimensions'}
                </p>
                {conversions.map((conversion) => (
                  <Button
                    key={conversion.label}
                    variant="ghost"
                    size="sm"
                    className="w-full justify-start text-xs h-7"
                    onClick={() => handleConversion(conversion)}
                  >
                    {conversion.label}
                  </Button>
                ))}
              </div>
            </PopoverContent>
          </Popover>
        )}
        {pinButton}
      </div>
    </div>
  );
});

UnitConversionColumnHeader.displayName = 'UnitConversionColumnHeader';

/**
 * DefaultValueColumnHeader Component
 *
 * Renders a column header with a hover button that sets a default value for all rows.
 * Used for Tax Category ("Not Specifically Set") and Shipping Restrictions ("None").
 *
 * PERFORMANCE: Uses local hover state and getState() for bulk updates.
 */
interface DefaultValueColumnHeaderProps {
  fieldKey: 'tax_cat' | 'ship_restrictions';
  label: string;
  isRequired: boolean;
  pinButton?: React.ReactNode;
  isPinned?: boolean;
}

const DEFAULT_VALUE_CONFIG: Record<string, { value: string; displayName: string; buttonLabel: string }> = {
  tax_cat: { value: '0', displayName: 'Not Specifically Set', buttonLabel: 'Set All Default' },
  ship_restrictions: { value: '0', displayName: 'None', buttonLabel: 'Set All None' },
};

const DefaultValueColumnHeader = memo(({ fieldKey, label, isRequired, pinButton }: DefaultValueColumnHeaderProps) => {
  const [isHovered, setIsHovered] = useState(false);
  const [hasEmptyCells, setHasEmptyCells] = useState(false);

  const config = DEFAULT_VALUE_CONFIG[fieldKey];

  // Check if there are any empty cells that can be filled
  const checkEmptyCells = useCallback(() => {
    const { rows } = useValidationStore.getState();
    return rows.some((row) => {
      const value = row[fieldKey];
      return value === undefined || value === null || value === '';
    });
  }, [fieldKey]);

  // Update empty check on hover
  const handleMouseEnter = useCallback(() => {
    setIsHovered(true);
    setHasEmptyCells(checkEmptyCells());
  }, [checkEmptyCells]);

  const handleSetDefault = useCallback(() => {
    const updatedIndices: number[] = [];

    useValidationStore.setState((draft) => {
      draft.rows.forEach((row, index) => {
        const value = row[fieldKey];
        const isEmpty = value === undefined || value === null || value === '';

        if (isEmpty) {
          draft.rows[index][fieldKey] = config.value;
          updatedIndices.push(index);
        }
      });
    });

    if (updatedIndices.length > 0) {
      const { clearFieldError } = useValidationStore.getState();
      updatedIndices.forEach((rowIndex) => {
        clearFieldError(rowIndex, fieldKey);
      });

      toast.success(`Set ${updatedIndices.length} row${updatedIndices.length === 1 ? '' : 's'} to "${config.displayName}"`);
    }
  }, [fieldKey, config]);

  return (
    <div
      className="flex items-center gap-1 min-w-0 w-full group relative"
      onMouseEnter={handleMouseEnter}
      onMouseLeave={() => setIsHovered(false)}
    >
      <span className="truncate">{label}</span>
      {isRequired && (
        <span className="text-destructive flex-shrink-0">*</span>
      )}
      {/* Button group: pin button always visible when pinned, action buttons only on hover */}
      <div className="absolute right-0 top-1/2 -translate-y-1/2 flex items-center gap-1">
        {isHovered && hasEmptyCells && (
          <TooltipProvider>
            <Tooltip>
              <TooltipTrigger asChild>
                <button
                  type="button"
                  onClick={(e) => {
                    e.stopPropagation();
                    handleSetDefault();
                  }}
                  className={cn(
                    'flex items-center gap-0.5',
                    'rounded border border-input bg-background p-1 shadow-sm',
                    'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
                    'whitespace-nowrap'
                  )}
                >
                  <Wand2 className="h-3 w-3" />
                </button>
              </TooltipTrigger>
              <TooltipContent side="bottom">
                <p>Fill empty cells with "{config.displayName}"</p>
              </TooltipContent>
            </Tooltip>
          </TooltipProvider>
        )}
        {pinButton}
      </div>
    </div>
  );
});

DefaultValueColumnHeader.displayName = 'DefaultValueColumnHeader';

/**
 * PinButton Component - renders a pin/unpin toggle for any column header.
 * Hidden by default, visible on column header hover (via parent `group` class).
 * Always visible when the column is pinned.
 */
interface PinButtonProps {
  isPinned: boolean;
  onToggle: () => void;
}

const PinButton = memo(({ isPinned, onToggle }: PinButtonProps) => {
  return (
    <TooltipProvider>
      <Tooltip>
        <TooltipTrigger asChild>
          <button
            type="button"
            onClick={(e) => {
              e.stopPropagation();
              onToggle();
            }}
            className={cn(
              // Matches existing action button style (Calculator, Scale, Wand2)
              // No absolute positioning — parent container handles placement
              'flex items-center justify-center',
              'rounded border border-input bg-background p-1 shadow-sm',
              'text-muted-foreground hover:bg-accent hover:text-accent-foreground',
              'transition-opacity',
              // Visible when pinned, otherwise show on hover (via parent group)
              isPinned ? 'opacity-100' : 'opacity-0 group-hover:opacity-100'
            )}
          >
            {isPinned ? <Pin className="h-3 w-3" /> : <PinOff className="h-3 w-3" />}
          </button>
        </TooltipTrigger>
        <TooltipContent side="bottom">
          <p>{isPinned ? 'Unpin column' : 'Pin column'}</p>
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  );
});

PinButton.displayName = 'PinButton';

/**
 * Main table component
 *
 * CRITICAL PERFORMANCE: We only subscribe to rows.length, NOT the rows array itself.
 * When a cell value changes, Immer creates a new rows array reference, but the length
 * stays the same. By only subscribing to length, cell edits don't cascade re-renders.
 */
export const ValidationTable = () => {
  const fields = useFields();
  const filters = useFilters();

  // CRITICAL: Only subscribe to row COUNT, not the rows array
  // This prevents re-renders when cell values change
  const rowCount = useValidationStore((state) => state.rows.length);

  // Refs for scroll sync
  const tableContainerRef = useRef<HTMLDivElement>(null);
  const headerRef = useRef<HTMLDivElement>(null);

  // Set of currently pinned field keys — defaults to 'name' pinned (same as before)
  const [pinnedColumns, setPinnedColumns] = useState<Set<string>>(() => new Set(['name']));


  // Toggle pin for a column — enforces MAX_PINNED_COLUMNS cap
  const togglePinColumn = useCallback((fieldKey: string) => {
    setPinnedColumns(prev => {
      const next = new Set(prev);
      if (next.has(fieldKey)) {
        next.delete(fieldKey);
      } else {
        if (next.size >= MAX_PINNED_COLUMNS) {
          toast.warning(`Maximum of ${MAX_PINNED_COLUMNS} pinned columns`);
          return prev;
        }
        next.add(fieldKey);
      }
      return next;
    });
  }, []);

  // Compute layout info for each pinned column: cumulative left/right offsets, natural position, width, z-index
  // Pinned columns maintain their natural field order (no reordering)
  const pinnedColumnLayout = useMemo(() => {
    const layout = new Map<string, PinnedColumnEntry>();
    if (pinnedColumns.size === 0) return layout;

    // Collect pinned fields in field order with their natural left positions
    const baseOffset = 40 + TEMPLATE_COLUMN_WIDTH; // Selection + Template columns
    const pinnedInOrder: { key: string; width: number; naturalLeft: number }[] = [];
    let runningOffset = baseOffset;
    for (const field of fields) {
      const w = field.width || 150;
      if (pinnedColumns.has(field.key)) {
        pinnedInOrder.push({ key: field.key, width: w, naturalLeft: runningOffset });
      }
      runningOffset += w;
    }

    // Compute cumulative right offsets (from the right edge inward)
    const rightOffsets: number[] = new Array(pinnedInOrder.length).fill(0);
    let cumulativeRight = 0;
    for (let i = pinnedInOrder.length - 1; i >= 0; i--) {
      rightOffsets[i] = cumulativeRight;
      cumulativeRight += pinnedInOrder[i].width;
    }

    // Assign cumulative left offsets, right offsets, and z-indexes
    let cumulativeLeft = 0;
    for (let i = 0; i < pinnedInOrder.length; i++) {
      const { key, width, naturalLeft } = pinnedInOrder[i];
      layout.set(key, {
        left: cumulativeLeft,
        right: rightOffsets[i],
        naturalLeft,
        width,
        zIndex: 10 + i,        // Increasing z so rightmost pinned is on top
      });
      cumulativeLeft += width;
    }
    return layout;
  }, [fields, pinnedColumns]);

  // Per-column sticky state: each pinned column independently sticks left, right, or not at all.
  // Only columns that are actually stuck appear in this map.
  const [stickyStates, setStickyStates] = useState<Map<string, 'left' | 'right'>>(() => new Map());
  const stickyStatesRef = useRef<Map<string, 'left' | 'right'>>(new Map());
  // Shadow keys: the outermost stuck column on each side
  const [shadowLeftKey, setShadowLeftKey] = useState<string | null>(null);
  const [shadowRightKey, setShadowRightKey] = useState<string | null>(null);
  const shadowLeftRef = useRef<string | null>(null);
  const shadowRightRef = useRef<string | null>(null);

  // Compute per-column sticky state and shadow keys from scroll position
  const updateStickyState = useCallback((scrollLeft: number, viewportWidth: number) => {
    let newLeftShadow: string | null = null;
    let newRightShadow: string | null = null;
    let changed = false;
    const newStates = new Map<string, 'left' | 'right'>();

    for (const [key, entry] of pinnedColumnLayout) {
      // CSS sticky; left: L engages when naturalLeft < scrollLeft + L
      const stuckLeft = entry.naturalLeft < scrollLeft + entry.left;
      // CSS sticky; right: R engages when naturalLeft + width > scrollLeft + viewportWidth - R
      const stuckRight = entry.naturalLeft + entry.width > scrollLeft + viewportWidth - entry.right;

      if (stuckLeft) {
        newStates.set(key, 'left');
        newLeftShadow = key; // Keep overwriting — last match = rightmost stuck-left
      } else if (stuckRight) {
        newStates.set(key, 'right');
        if (newRightShadow === null) newRightShadow = key; // First match = leftmost stuck-right
      }
    }

    // Compare with previous states to avoid unnecessary re-renders
    if (newStates.size !== stickyStatesRef.current.size) {
      changed = true;
    } else {
      for (const [key, dir] of newStates) {
        if (stickyStatesRef.current.get(key) !== dir) { changed = true; break; }
      }
    }

    if (changed) {
      stickyStatesRef.current = newStates;
      setStickyStates(newStates);
    }
    if (newLeftShadow !== shadowLeftRef.current) {
      shadowLeftRef.current = newLeftShadow;
      setShadowLeftKey(newLeftShadow);
    }
    if (newRightShadow !== shadowRightRef.current) {
      shadowRightRef.current = newRightShadow;
      setShadowRightKey(newRightShadow);
    }
  }, [pinnedColumnLayout]);

  // Body scroll → sync header + update sticky state
  const handleScroll = useCallback(() => {
    if (tableContainerRef.current && headerRef.current) {
      const scrollLeft = tableContainerRef.current.scrollLeft;
      const viewportWidth = tableContainerRef.current.clientWidth;
      if (headerRef.current.scrollLeft !== scrollLeft) {
        headerRef.current.scrollLeft = scrollLeft;
      }
      updateStickyState(scrollLeft, viewportWidth);
    }
  }, [updateStickyState]);

  // Header scroll → sync body (handles scroll-wheel over header area)
  const handleHeaderScroll = useCallback(() => {
    if (headerRef.current && tableContainerRef.current) {
      const scrollLeft = headerRef.current.scrollLeft;
      if (tableContainerRef.current.scrollLeft !== scrollLeft) {
        tableContainerRef.current.scrollLeft = scrollLeft;
      }
    }
  }, []);

  // Compute filtered indices AND row IDs in a single pass
  // This avoids calling getState() during render for each row
  const { filteredIndices, rowIdMap } = useMemo(() => {
    const state = useValidationStore.getState();
    const idMap = new Map<number, string>();

    if (!filters.searchText && !filters.showErrorsOnly) {
      // No filtering - return all indices with their row IDs
      const indices = Array.from({ length: rowCount }, (_, i) => {
        const rowId = state.rows[i]?.__index;
        if (rowId) idMap.set(i, rowId);
        return i;
      });
      return { filteredIndices: indices, rowIdMap: idMap };
    }

    const indices: number[] = [];

    state.rows.forEach((row, index) => {
      // Apply search filter
      if (filters.searchText) {
        const searchLower = filters.searchText.toLowerCase();
        const matches = Object.values(row).some((value) =>
          String(value ?? '').toLowerCase().includes(searchLower)
        );
        if (!matches) return;
      }

      // Apply errors-only filter
      if (filters.showErrorsOnly) {
        const rowErrors = state.errors.get(index);
        if (!rowErrors || Object.keys(rowErrors).length === 0) return;
      }

      indices.push(index);
      if (row.__index) idMap.set(index, row.__index);
    });

    return { filteredIndices: indices, rowIdMap: idMap };
  }, [rowCount, filters.searchText, filters.showErrorsOnly]);

  // Build columns - ONLY depends on fields and pinnedColumns, NOT selection state
  // Selection state is handled by isolated HeaderCheckbox component
  const columns = useMemo<ColumnDef<RowData>[]>(() => {
    // Selection column - uses isolated HeaderCheckbox to prevent cascading re-renders
    const selectionColumn: ColumnDef<RowData> = {
      id: 'select',
      header: () => <HeaderCheckbox />,
      size: 40,
    };

    // Template column
    const templateColumn: ColumnDef<RowData> = {
      id: 'template',
      header: () => <span className="truncate">Template</span>,
      size: TEMPLATE_COLUMN_WIDTH,
    };

    // Data columns from fields with widths from config.ts
    const dataColumns: ColumnDef<RowData>[] = fields.map((field) => {
      const isRequired = field.validations?.some((v: Validation) => v.rule === 'required') ?? false;
      const isPriceColumn = field.key === 'msrp' || field.key === 'cost_each';
      const isUnitConversionColumn = field.key === 'weight' || field.key === 'length' || field.key === 'width' || field.key === 'height';
      const isDefaultValueColumn = field.key === 'tax_cat' || field.key === 'ship_restrictions';
      const isPinned = pinnedColumns.has(field.key);

      // Pin button passed to each header — rendered alongside action buttons
      const pinBtn = (
        <PinButton
          isPinned={isPinned}
          onToggle={() => togglePinColumn(field.key)}
        />
      );

      // Determine which header component to render
      const renderHeader = () => {
        if (isPriceColumn) {
          return (
            <PriceColumnHeader
              fieldKey={field.key as 'msrp' | 'cost_each'}
              label={field.label}
              isRequired={isRequired}
              pinButton={pinBtn}
              isPinned={isPinned}
            />
          );
        }
        if (isUnitConversionColumn) {
          return (
            <UnitConversionColumnHeader
              fieldKey={field.key as 'weight' | 'length' | 'width' | 'height'}
              label={field.label}
              isRequired={isRequired}
              pinButton={pinBtn}
              isPinned={isPinned}
            />
          );
        }
        if (isDefaultValueColumn) {
          return (
            <DefaultValueColumnHeader
              fieldKey={field.key as 'tax_cat' | 'ship_restrictions'}
              label={field.label}
              isRequired={isRequired}
              pinButton={pinBtn}
              isPinned={isPinned}
            />
          );
        }
        // Plain header — pin button in its own absolutely-positioned container
        return (
          <div className="flex items-center gap-1 min-w-0 w-full group relative">
            <span className="truncate">{field.label}</span>
            {isRequired && (
              <span className="text-destructive flex-shrink-0">*</span>
            )}
            <div className={cn(
              'absolute right-0 top-1/2 -translate-y-1/2',
              'transition-opacity',
              !isPinned && 'opacity-0 group-hover:opacity-100'
            )}>
              {pinBtn}
            </div>
          </div>
        );
      };

      return {
        id: field.key,
        header: renderHeader,
        size: field.width || 150,
      };
    });

    return [selectionColumn, templateColumn, ...dataColumns];
  }, [fields, pinnedColumns, togglePinColumn]);

  // Calculate total table width for horizontal scrolling
  const totalTableWidth = useMemo(() => {
    return columns.reduce((sum, col) => sum + (col.size || 150), 0);
  }, [columns]);

  // Virtual row renderer
  // Lower overscan = faster initial load, slight flicker on fast scroll
  const rowVirtualizer = useVirtualizer({
    count: filteredIndices.length,
    getScrollElement: () => tableContainerRef.current,
    estimateSize: () => ROW_HEIGHT,
    overscan: 3,
  });

  const virtualRows = rowVirtualizer.getVirtualItems();
  const totalSize = rowVirtualizer.getTotalSize();

  return (
    <div className="h-full flex flex-col border rounded-md overflow-hidden relative">
      {/* Copy-down banner - shows when copy-down mode is active */}
      <CopyDownBanner />

      {/* Fixed header - syncs horizontal scroll bidirectionally with body */}
      <div
        ref={headerRef}
        className="flex-shrink-0 bg-muted/50 border-b overflow-x-auto overflow-y-hidden [&::-webkit-scrollbar]:hidden [scrollbar-width:none]"
        style={{ height: HEADER_HEIGHT }}
        onScroll={handleHeaderScroll}
      >
        <div
          className="flex h-full"
          style={{ minWidth: totalTableWidth }}
        >
          {columns.map((column, index) => {
            // Per-column sticky state (guard: pinnedEntry must exist — stickyStates can lag behind unpins)
            const pinnedEntry = column.id ? pinnedColumnLayout.get(column.id) : undefined;
            const columnStickDir = pinnedEntry && column.id ? stickyStates.get(column.id) : undefined;
            const shouldBeSticky = !!columnStickDir;
            const stickyLeft = columnStickDir === 'left';
            const stickyRight = columnStickDir === 'right';
            const isShadowColumn = column.id === shadowLeftKey || column.id === shadowRightKey;

            return (
              <div
                key={column.id || index}
                className={cn(
                  "px-3 flex items-center text-left text-sm font-medium text-muted-foreground",
                  // Use box-shadow for right border - renders more consistently
                  "shadow-[inset_-1px_0_0_0_hsl(var(--border))] last:shadow-none",
                  // Sticky header - only when enabled and scrolled appropriately
                  shouldBeSticky && "lg:sticky lg:[background:linear-gradient(hsl(var(--muted)/0.5),hsl(var(--muted)/0.5)),hsl(var(--background))]",
                  // Drop shadow only on the outermost actually-stuck column per side
                  isShadowColumn && stickyLeft && "lg:shadow-[inset_-1px_0_0_0_hsl(var(--border)),2px_0_4px_-2px_rgba(0,0,0,0.15)]",
                  isShadowColumn && stickyRight && "lg:shadow-[inset_-1px_0_0_0_hsl(var(--border)),inset_1px_0_0_0_hsl(var(--border)),-2px_0_4px_-2px_rgba(0,0,0,0.15)]",
                )}
                style={{
                  width: column.size || 150,
                  minWidth: column.size || 150,
                  flexShrink: 0,
                  // Z-index layered per pinned column (header z is 10 above cell z)
                  ...(shouldBeSticky && { zIndex: pinnedEntry!.zIndex + 10 }),
                  // Position sticky left or right based on scroll direction
                  ...(stickyLeft && { left: pinnedEntry!.left }),
                  ...(stickyRight && { right: pinnedEntry!.right }),
                }}
              >
                {typeof column.header === 'function'
                  ? column.header({} as any)
                  : column.header}
              </div>
            );
          })}
        </div>
      </div>

      {/* Scrollable virtualized content */}
      <div ref={tableContainerRef} className="flex-1 overflow-auto" onScroll={handleScroll}>
        <div
          style={{
            height: totalSize,
            width: totalTableWidth,
            minWidth: totalTableWidth,
            position: 'relative',
          }}
        >
          {/* Virtual rows - use pre-computed rowIdMap to avoid getState() during render */}
          {virtualRows.map((virtualRow) => {
            const actualRowIndex = filteredIndices[virtualRow.index];
            const rowId = rowIdMap.get(actualRowIndex);
            if (!rowId) return null;

            return (
              <VirtualRow
                key={rowId}
                rowIndex={actualRowIndex}
                rowId={rowId}
                virtualStart={virtualRow.start}
                columns={columns}
                fields={fields}
                totalRowCount={rowCount}
                pinnedColumnLayout={pinnedColumnLayout}
                stickyStates={stickyStates}
                shadowLeftKey={shadowLeftKey}
                shadowRightKey={shadowRightKey}
              />
            );
          })}
        </div>
      </div>
    </div>
  );
};
