/**
 * FormGeneratorTable — Backend-driven data table.
 *
 * ARCHITECTURE:
 * This table does NO client-side filtering, sorting, or pagination.
 * All data processing is delegated to the backend via hookData.refetch().
 * The `data` prop is rendered as-is (displayData = data).
 *
 * REQUIRED CONTRACT for interactive features (search, filter, sort, pagination):
 *
 *   hookData={{
 *     refetch,    // (params?: PaginationParams) => Promise<void>
 *                 //   Called on every search/filter/sort/page change.
 *                 //   Must fetch from backend with pagination query param
 *                 //   and update the data + pagination states.
 *     pagination, // { currentPage, pageSize, totalItems, totalPages } | null
 *                 //   Drives pagination controls. Comes from backend response.
 *     fetchFilterValues, // (columnKey: string) => Promise<string[]> (Optional)
 *                        //   If provided, called when a filter dropdown opens.
 *                        //   If NOT provided but apiEndpoint is set, the table
 *                        //   auto-fetches from `{apiEndpoint}/filter-values?column=xxx`.
 *   }}
 *
 * Without hookData.refetch, interactive controls (sort, filter, search,
 * pagination) are inert — the table renders data but actions have no effect.
 *
 * FILTER VALUES (autofilter):
 *   When a filterable column's dropdown opens, distinct values are loaded from:
 *   1. column.filterOptions (static enum — used as-is, no backend call)
 *   2. hookData.fetchFilterValues(columnKey) if provided
 *   3. GET {apiEndpoint}/filter-values?column=xxx&pagination={currentFilters}
 *   Cross-filtering is supported: changing a filter invalidates the cache,
 *   so re-opening another column's dropdown re-fetches with current filters.
 *   Boolean columns render as "Ja"/"Nein"; date columns render as range picker.
 *
 * BACKEND RESPONSE FORMAT (for refetch):
 *   { items: T[], pagination: PaginationMetadata | null }
 *
 * BACKEND RESPONSE FORMAT (for filter-values):
 *   string[]
 *
 * EXAMPLE (minimal integration):
 *
 *   const { data, pagination, loading, refetch } = useMyEntityHook();
 *
 *   <FormGeneratorTable
 *     data={data}
 *     columns={columns}
 *     loading={loading}
 *     hookData={{ refetch, pagination }}
 *     apiEndpoint="/api/my-entity/"    // enables CSV export + auto filter values
 *   />
 *
 * See useOrgUsers / AdminUsersPage for a full reference implementation.
 */
import React, { useState, useMemo, useRef, useEffect, useCallback } from 'react';
import type { IconType } from 'react-icons';
import { useLanguage } from '../../../providers/language/LanguageContext';
import styles from './FormGeneratorTable.module.css';
import { 
  EditActionButton, 
  DeleteActionButton, 
  ViewActionButton,
  CopyActionButton,
  CustomActionButton
} from '../ActionButtons';
import { formatUnixTimestamp } from '../../../utils/time';
import { FormGeneratorControls } from '../FormGeneratorControls';
import { CopyableTruncatedValue } from '../../UiComponents/CopyableTruncatedValue';
import {
  isDateTimeType,
  isCheckboxType
} from '../../../utils/attributeTypeMapper';
import type { AttributeType } from '../../../utils/attributeTypeMapper';
import { FaFilter } from 'react-icons/fa';
import api from '../../../api';

// FK Cache type: maps fkSource -> { id -> displayLabel }
type FkCacheType = Record<string, Record<string, string>>;

/**
 * Stringify any cell value for display.
 * The backend resolves TextMultilingual to plain strings via resolveText() / get_text().
 * If an unresolved object still arrives, extract xx as a safe fallback and log a warning.
 */
const _objectToDisplayString = (value: Record<string, unknown>): string => {
  if ('xx' in value && typeof value.xx === 'string' && value.xx.trim()) {
    console.warn('FormGeneratorTable: received unresolved TextMultilingual object — backend should call resolveText()', value);
    return value.xx as string;
  }
  for (const field of ['label', 'name', 'title', 'id', 'value', 'text']) {
    const v = value[field];
    if (v !== undefined && v !== null) return String(v);
  }
  try { return JSON.stringify(value); } catch { return String(value); }
};

// Types for the FormGeneratorTable
export interface ColumnConfig {
  key: string;
  label: string;
  type?: AttributeType;
  width?: number;
  minWidth?: number;
  maxWidth?: number;
  sortable?: boolean;
  filterable?: boolean;
  searchable?: boolean;
  formatter?: (value: any, row: any) => React.ReactNode;
  filterOptions?: string[]; // For enum/select filters
  cellClassName?: (value: any, row: any) => string; // For custom cell styling
  fkSource?: string; // API endpoint for FK resolution (e.g., "/api/users/")
  fkDisplayField?: string; // Which field of FK target to display (e.g., "username", "name", "roleLabel")
}

export interface FormGeneratorTableProps<T = any> {
  data: T[];
  columns?: ColumnConfig[];
  title?: string;
  searchable?: boolean;
  filterable?: boolean;
  sortable?: boolean;
  resizable?: boolean;
  pagination?: boolean;
  pageSize?: number;
  pageSizeOptions?: number[];
  showPageSizeSelector?: boolean;
  onRowClick?: (row: T, index: number) => void;
  onRowSelect?: (selectedRows: T[]) => void;
  selectable?: boolean;
  isRowSelectable?: (row: T) => boolean;
  loading?: boolean;
  // Inline editing configuration
  inlineEditable?: boolean; // Enable inline editing for supported field types (checkbox/boolean)
  onInlineUpdate?: (row: T, field: string, newValue: any) => Promise<void> | void; // Called when inline edit occurs
  inlineEditingRows?: Set<string>; // Set of row IDs currently being edited (for loading state)
  idField?: string; // Field name for unique row identifier (default: 'id')
  // Standard action buttons (edit, delete, view, copy, connect, play)
  actionButtons?: {
    type: 'edit' | 'delete' | 'view' | 'copy' | 'connect' | 'play';
    onAction?: (row: T) => Promise<void> | void; // Optional for delete buttons since they handle their own logic
    disabled?: (row: T, hookData?: any) => boolean | { disabled: boolean; message?: string };
    loading?: (row: T, hookData?: any) => boolean;
    title?: string | ((row: T) => string);
    className?: string;
    // For view buttons
    isProcessing?: (row: T, hookData?: any) => boolean;
    // Field mappings for flexible data access
    idField?: string; // Field name for the unique identifier
    nameField?: string; // Field name for display name
    typeField?: string; // Field name for type/mime type
    contentField?: string; // Field name for content (used by copy button)
    statusField?: string; // Field name for status (used by connect action)
    // Operation and loading state names
    operationName?: string; // Name of the operation function in hookData
    loadingStateName?: string; // Name of the loading state in hookData
    fetchItemFunctionName?: string; // Name of the function to fetch a single item (for edit button)
    // Navigation and mode (for play action)
    navigateTo?: string; // Path to navigate to after action
    mode?: string; // Mode to set (e.g., 'prompt', 'workflow')
  }[];
  // Custom action buttons (entity-specific actions like download, connect, play, sendPasswordLink)
  customActions?: {
    id: string; // Unique identifier for the action
    icon: React.ReactNode; // Icon component to display
    onClick: (row: T, hookData?: any) => Promise<void> | void; // Handler function
    visible?: (row: T, hookData?: any) => boolean; // Show/hide based on row data (default: true)
    disabled?: (row: T, hookData?: any) => boolean | { disabled: boolean; message?: string };
    loading?: (row: T, hookData?: any) => boolean; // Loading state based on row data
    title?: string | ((row: T) => string); // Tooltip text
    className?: string; // Optional custom CSS class
    idField?: string; // Field name for unique identifier (default: 'id')
  }[];
  onDelete?: (row: T) => void;
  onDeleteMultiple?: (rows: T[]) => void;
  /** Batch actions shown when rows are selected (e.g. Sync to Accounting). Selection is cleared on success. */
  batchActions?: {
    label: string;
    onClick: (rows: T[]) => void | Promise<void>;
    loading?: boolean;
    icon?: IconType;
  }[];
  onRefresh?: () => void;
  className?: string;
  getRowDataAttributes?: (row: T, index: number) => Record<string, string>;
  // For passing hook data to action buttons
  hookData?: any; // Contains all hook data: refetch, operations, loading states, etc.
  // Custom empty message when table is empty
  emptyMessage?: string;
  // API endpoint for CSV export (e.g. "/api/users/"). If provided, the CSV export button is shown.
  apiEndpoint?: string;
  // Grouping configuration
  groupBy?: string;
  groupRenderer?: (groupKey: string, groupRows: T[], isExpanded: boolean) => React.ReactNode;
  groupRowData?: (groupKey: string, groupRows: T[]) => Record<string, React.ReactNode>;
  groupDefaultExpanded?: boolean;
  groupActions?: (groupKey: string, groupRows: T[]) => React.ReactNode;
  initialSearchTerm?: string;
  rowDraggable?: boolean;
  onRowDragStart?: (e: React.DragEvent<HTMLTableRowElement>, row: T) => void;
}

const _FILTER_PAGE_SIZE = 100;

/**
 * Renders a scrollable list of filter values with IntersectionObserver-based lazy loading.
 * Shows _FILTER_PAGE_SIZE items initially, loads more as the user scrolls.
 */
function FilterValuesList({
  columnKey,
  allValues,
  activeFilter,
  onSelect,
  resolveLabel,
}: {
  columnKey: string;
  allValues: string[];
  activeFilter: any;
  onSelect: (value: string) => void;
  resolveLabel?: (value: string) => string;
}) {
  const [displayCount, setDisplayCount] = useState(_FILTER_PAGE_SIZE);
  const sentinelRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    setDisplayCount(_FILTER_PAGE_SIZE);
  }, [columnKey, allValues.length]);

  useEffect(() => {
    const sentinel = sentinelRef.current;
    if (!sentinel || displayCount >= allValues.length) return;

    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0]?.isIntersecting) {
          setDisplayCount(prev => Math.min(prev + _FILTER_PAGE_SIZE, allValues.length));
        }
      },
      { threshold: 0.1 }
    );
    observer.observe(sentinel);
    return () => observer.disconnect();
  }, [displayCount, allValues.length]);

  const visibleValues = allValues.slice(0, displayCount);

  return (
    <>
      {visibleValues.map(value => {
        const label = resolveLabel ? resolveLabel(value) : value;
        return (
          <div
            key={value}
            className={`${styles.filterOption} ${activeFilter === value ? styles.filterOptionSelected : ''}`}
            onClick={() => onSelect(value)}
            title={label}
          >
            {label.length > 30 ? label.substring(0, 30) + '...' : label}
          </div>
        );
      })}
      {displayCount < allValues.length && (
        <div ref={sentinelRef} style={{ height: 1, opacity: 0 }} />
      )}
    </>
  );
}

export function FormGeneratorTable<T extends Record<string, any>>({
  data,
  columns: providedColumns,
  searchable = true,
  filterable = true,
  sortable = true,
  resizable = true,
  pagination = true,
  pageSize = 10,
  pageSizeOptions = [10, 25, 50, 100, 500],
  showPageSizeSelector = true,
  onRowClick,
  onRowSelect,
  selectable = true, // Default to true for selection functionality
  isRowSelectable,
  loading = false,
  inlineEditable = true, // Enable inline editing by default
  onInlineUpdate,
  inlineEditingRows,
  idField = 'id',
  actionButtons = [],
  customActions = [],
  onDelete,
  onDeleteMultiple: onDeleteMultipleProp,
  batchActions = [],
  onRefresh,
  className = '',
  getRowDataAttributes,
  hookData,
  emptyMessage,
  apiEndpoint,
  groupBy,
  groupRenderer: _groupRenderer,
  groupRowData,
  groupDefaultExpanded = true,
  groupActions,
  initialSearchTerm = '',
  rowDraggable = false,
  onRowDragStart,
}: FormGeneratorTableProps<T>) {
  const { t, currentLanguage: contextLanguage } = useLanguage();
  // When only onDelete is provided, use it for multi-delete too so Delete stays visible with 2+ selected
  const onDeleteMultiple = onDeleteMultipleProp ?? (onDelete ? (rows: T[]) => rows.forEach((r) => onDelete(r)) : undefined);
  const currentLanguage = useMemo(() => contextLanguage || 'en', [contextLanguage]);
  // Use provided columns from Pydantic attribute definitions
  // NO AUTO-DETECTION - columns must come from backend attribute definitions
  // Use a ref to cache columns so they persist across data changes (e.g., when filtering)
  const columnsRef = useRef<ColumnConfig[]>([]);
  
  const detectedColumns = useMemo((): ColumnConfig[] => {
    // Use providedColumns from Pydantic attribute definitions
    if (providedColumns && providedColumns.length > 0) {
      columnsRef.current = providedColumns;
      return providedColumns;
    }
    
    // Use cached columns if data becomes empty (e.g., after filtering)
    if (columnsRef.current.length > 0) {
      return columnsRef.current;
    }
    
    // NO COLUMNS PROVIDED - this is an error in the calling component
    // The calling component should provide columns from the /attributes/{entityType} endpoint
    console.warn(
      '⚠️ FormGeneratorTable: No columns provided! ' +
      'Columns should come from Pydantic attribute definitions via /attributes/{entityType} endpoint. ' +
      'Please ensure the calling component fetches and passes columns from the backend.'
    );
    
    // Return empty array - table will show no columns
    return [];
  }, [providedColumns, data]);

  // State management
  const [searchTerm, setSearchTerm] = useState(initialSearchTerm);
  const [searchFocused, setSearchFocused] = useState(false);
  const [filterFocused, setFilterFocused] = useState<Record<string, boolean>>({});
  // Multi-column sorting: array of sort configs in order of priority
  const [sortConfigs, setSortConfigs] = useState<Array<{ key: string; direction: 'asc' | 'desc' }>>([]);
  const [filters, setFilters] = useState<Record<string, any>>({});
  const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
  // Actions column width - resizable, default based on number of buttons
  const [actionsColumnWidth, setActionsColumnWidth] = useState<number | null>(null);
  const [selectedRows, setSelectedRows] = useState<Set<number>>(new Set());
  const [currentPage, setCurrentPage] = useState(1);
  const [currentPageSize, setCurrentPageSize] = useState(pageSize);
  const [openFilterColumn, setOpenFilterColumn] = useState<string | null>(null);
  const filterDropdownRef = useRef<HTMLDivElement>(null);
  
  // Grouping: Track expanded groups
  const [expandedGroups, setExpandedGroups] = useState<Set<string>>(() => new Set());
  const [groupsInitialized, setGroupsInitialized] = useState(false);
  
  // FK Resolution: Cache for resolved FK values (fkSource -> { id -> displayLabel })
  const [fkCache, setFkCache] = useState<FkCacheType>({});
  const [fkLoading, setFkLoading] = useState<Record<string, boolean>>({});
  const fkLoadedSourcesRef = useRef<Set<string>>(new Set());
  
  // Generate a storage key based on column names for localStorage persistence
  const storageKey = useMemo(() => {
    if (detectedColumns.length === 0) return null;
    const columnNames = detectedColumns.map(c => c.key).sort().join(',');
    return `table-column-widths-${columnNames.substring(0, 50)}`; // Limit key length
  }, [detectedColumns]);
  
  // Track if we've loaded from localStorage for this storage key
  const loadedStorageKeyRef = useRef<string | null>(null);
  
  // Check if backend pagination is supported (hookData has refetch that accepts params).
  const supportsBackendPagination = hookData?.refetch && typeof hookData.refetch === 'function';
  
  // Debounce search term for backend calls
  const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm);
  
  useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedSearchTerm(prev => {
        if (prev !== searchTerm) setCurrentPage(1);
        return searchTerm;
      });
    }, 300);
    
    return () => clearTimeout(timer);
  }, [searchTerm]);
  
  // Call backend when filters/search/sort/pagination change
  useEffect(() => {
    if (!supportsBackendPagination || !hookData?.refetch) return;
    
    // Build pagination parameters
    const paginationParams: any = {
      page: currentPage,
      pageSize: currentPageSize,
    };
    
    // Add search if provided
    if (debouncedSearchTerm && debouncedSearchTerm.trim()) {
      paginationParams.search = debouncedSearchTerm.trim();
    }
    
    // Add filters if provided
    // Note: Date/timestamp filters are disabled in column config, so they won't appear here
    const activeFilters: Record<string, any> = {};
    Object.entries(filters).forEach(([key, value]) => {
      if (value !== undefined && value !== '') {
        activeFilters[key] = value;
      }
    });
    if (Object.keys(activeFilters).length > 0) {
      paginationParams.filters = activeFilters;
    }
    
    // Add sort if provided (multi-column sorting)
    if (sortConfigs.length > 0) {
      paginationParams.sort = sortConfigs.map(sc => ({
        field: sc.key,
        direction: sc.direction
      }));
    }
    
    // Log search parameters being sent to backend
    console.log('🔍 FormGeneratorTable: Calling backend with pagination params:', {
      searchTerm: debouncedSearchTerm,
      searchInParams: paginationParams.search,
      filters: paginationParams.filters,
      sort: paginationParams.sort,
      page: paginationParams.page,
      pageSize: paginationParams.pageSize,
      fullParams: paginationParams
    });
    
    // Call backend refetch with parameters
    hookData.refetch(paginationParams).then(() => {
      console.log('✅ FormGeneratorTable: Backend refetch completed');
    }).catch((error: any) => {
      console.error('❌ FormGeneratorTable: Backend refetch failed:', error);
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [debouncedSearchTerm, filters, sortConfigs, currentPage, currentPageSize, supportsBackendPagination]);
  
  // Refs for action buttons containers to detect clicks outside
  const actionButtonsRefs = useRef<Map<number, HTMLDivElement>>(new Map());
  
  // Refs for resizing
  const tableRef = useRef<HTMLTableElement>(null);
  const tableContainerRef = useRef<HTMLDivElement>(null);
  
  // Refs for top scrollbar synchronization
  const topScrollbarRef = useRef<HTMLDivElement>(null);
  const topScrollbarInnerRef = useRef<HTMLDivElement>(null);
  const isScrollingSyncRef = useRef<boolean>(false); // Prevent scroll sync loops
  
  // Track container width for actions column 20% threshold
  const [containerWidth, setContainerWidth] = useState<number>(0);
  
  // Whether the actions column should be shown (standard or custom actions present)
  const hasActionColumn = actionButtons.length > 0 || customActions.length > 0;
  
  // Calculate default actions column width and track container width
  // Minimum width always fits 4 icons (4 * 26px button + 3 * 2px gap + 8px padding = 122px)
  const MIN_ACTIONS_WIDTH_FOR_4_ICONS = 122;
  const defaultActionsWidth = useMemo(() => {
    if (actionButtons.length === 0 && customActions.length === 0) return 0;
    const totalButtons = actionButtons.length + customActions.length;
    const calculatedWidth = totalButtons * 26 + (totalButtons - 1) * 2 + 8;
    return Math.max(MIN_ACTIONS_WIDTH_FOR_4_ICONS, calculatedWidth);
  }, [actionButtons.length, customActions.length]);
  
  // Current actions column width (user-defined or default)
  const currentActionsWidth = actionsColumnWidth ?? defaultActionsWidth;
  
  // Check if actions column exceeds 20% of container width (enable wrapping)
  const shouldWrapActionButtons = containerWidth > 0 && currentActionsWidth > containerWidth * 0.20;
  
  // Track container width changes
  useEffect(() => {
    const container = tableContainerRef.current;
    if (!container) return;
    
    const updateContainerWidth = () => {
      setContainerWidth(container.clientWidth);
    };
    
    // Initial measurement
    updateContainerWidth();
    
    // Observe resize
    const resizeObserver = new ResizeObserver(updateContainerWidth);
    resizeObserver.observe(container);
    
    return () => resizeObserver.disconnect();
  }, []);
  const resizingColumn = useRef<string | null>(null);
  const startX = useRef<number>(0);
  const startWidth = useRef<number>(0);

  // Initialize column widths - load from localStorage or set defaults
  useEffect(() => {
    if (detectedColumns.length === 0 || !storageKey) return;
    
    // Only load from localStorage once per storage key
    if (loadedStorageKeyRef.current === storageKey) {
      // Already loaded for this key, just add defaults for any new columns
      setColumnWidths(prev => {
        const newWidths = { ...prev };
        let hasChanges = false;
        
        detectedColumns.forEach(col => {
          if (newWidths[col.key] === undefined) {
            newWidths[col.key] = col.width || 150;
            hasChanges = true;
          }
        });
        
        return hasChanges ? newWidths : prev;
      });
      return;
    }
    
    // Load from localStorage for this new storage key
    loadedStorageKeyRef.current = storageKey;
    
    try {
      const saved = localStorage.getItem(storageKey);
      if (saved) {
        const savedWidths = JSON.parse(saved);
        // Merge saved widths with defaults for any missing columns
        const mergedWidths: Record<string, number> = {};
        detectedColumns.forEach(col => {
          mergedWidths[col.key] = savedWidths[col.key] ?? col.width ?? 150;
        });
        setColumnWidths(mergedWidths);
        return;
      }
    } catch {
      // Ignore localStorage errors
    }
    
    // No saved widths, set defaults
    const defaultWidths: Record<string, number> = {};
    detectedColumns.forEach(col => {
      defaultWidths[col.key] = col.width || 150;
    });
    setColumnWidths(defaultWidths);
  }, [detectedColumns, storageKey]);
  
  // Save column widths to localStorage when they change (debounced via resize completion)
  const saveColumnWidthsToStorage = useRef((widths: Record<string, number>, key: string | null) => {
    if (Object.keys(widths).length > 0 && key) {
      try {
        localStorage.setItem(key, JSON.stringify(widths));
      } catch {
        // Ignore localStorage errors
      }
    }
  }).current;

  const convertToDisplayString = useCallback((fieldValue: any, _language: string): string => {
    if (fieldValue === null || fieldValue === undefined) {
      return '-';
    }
    
    // Boolean → language-neutral symbols (✓/✗)
    if (typeof fieldValue === 'boolean') {
      return fieldValue ? '✓' : '✗';
    }
    
    // Number → String
    if (typeof fieldValue === 'number') {
      return String(fieldValue);
    }
    
    // String → direct
    if (typeof fieldValue === 'string') {
      return fieldValue;
    }
    
    if (typeof fieldValue === 'object' && fieldValue !== null) {
      return _objectToDisplayString(fieldValue as Record<string, unknown>);
    }
    
    // Fallback
    return String(fieldValue);
  }, []);

  // FK Resolution: Load FK data in bulk for columns with fkSource
  useEffect(() => {
    if (data.length === 0 || detectedColumns.length === 0) return;

    // Find columns with fkSource that haven't been loaded yet
    const fkColumns = detectedColumns.filter(col => 
      col.fkSource && !fkLoadedSourcesRef.current.has(col.fkSource)
    );

    if (fkColumns.length === 0) return;

    // For each FK column, collect unique IDs from data and fetch them
    const loadFkData = async () => {
      for (const column of fkColumns) {
        const fkSource = column.fkSource!;
        const displayField = column.fkDisplayField; // Explicit field from Pydantic model
        
        // Skip if already loading
        if (fkLoading[fkSource]) continue;

        // Collect unique IDs from data for this column
        const uniqueIds = new Set<string>();
        data.forEach(row => {
          const value = row[column.key];
          if (value && typeof value === 'string' && value.length > 0) {
            uniqueIds.add(value);
          }
        });

        if (uniqueIds.size === 0) {
          fkLoadedSourcesRef.current.add(fkSource);
          continue;
        }

        // Mark as loading
        setFkLoading(prev => ({ ...prev, [fkSource]: true }));

        try {
          // Fetch all items from the FK source endpoint
          const response = await api.get(fkSource);
          
          // Build cache: id -> display label
          const cacheForSource: Record<string, string> = {};
          const items = Array.isArray(response.data) ? response.data : response.data?.items || [];
          
          items.forEach((item: any) => {
            if (!item || !item.id) return;
            
            let displayLabel = item.id; // Fallback to ID
            
            // Use the EXPLICIT display field from Pydantic model (fkDisplayField)
            if (displayField && item[displayField] !== undefined) {
              displayLabel = convertToDisplayString(item[displayField], currentLanguage);
            } else {
              // Fallback: if no displayField specified, try common fields
              // This should rarely happen if models are properly configured
              const fallbackFields = ['name', 'label', 'username', 'roleLabel', 'title'];
              for (const field of fallbackFields) {
                if (item[field] !== undefined) {
                  displayLabel = convertToDisplayString(item[field], currentLanguage);
                  break;
                }
              }
            }
            
            cacheForSource[item.id] = displayLabel;
          });

          // Update cache
          setFkCache(prev => ({
            ...prev,
            [fkSource]: { ...(prev[fkSource] || {}), ...cacheForSource }
          }));

          // Mark as loaded
          fkLoadedSourcesRef.current.add(fkSource);
        } catch (error) {
          console.error(`Failed to load FK data from ${fkSource}:`, error);
          // Mark as loaded to prevent infinite retries
          fkLoadedSourcesRef.current.add(fkSource);
        } finally {
          setFkLoading(prev => ({ ...prev, [fkSource]: false }));
        }
      }
    };

    loadFkData();
  }, [data, detectedColumns, currentLanguage, fkLoading, convertToDisplayString]);

  // Helper function to resolve FK value to display label
  const resolveFkValue = useCallback((value: string, fkSource: string): string => {
    const sourceCache = fkCache[fkSource];
    if (sourceCache && sourceCache[value]) {
      return sourceCache[value];
    }
    // Return truncated ID while loading or if not found
    return value.length > 8 ? `${value.substring(0, 8)}...` : value;
  }, [fkCache]);

  // Data is already filtered, sorted, and paginated by the backend
  // No client-side processing needed
  const displayData = data;

  // Grouping: Group data by groupBy field if specified
  const groupedData = useMemo(() => {
    if (!groupBy || !displayData || displayData.length === 0) {
      return null;
    }
    
    const groups = new Map<string, T[]>();
    for (const row of displayData) {
      const groupKey = String(row[groupBy] || '');
      if (!groups.has(groupKey)) {
        groups.set(groupKey, []);
      }
      groups.get(groupKey)!.push(row);
    }
    
    return groups;
  }, [groupBy, displayData]);

  // Initialize expanded groups when data changes
  useEffect(() => {
    if (groupBy && groupedData && !groupsInitialized) {
      if (groupDefaultExpanded) {
        setExpandedGroups(new Set(groupedData.keys()));
      }
      setGroupsInitialized(true);
    }
  }, [groupBy, groupedData, groupDefaultExpanded, groupsInitialized]);

  // Reset groups initialization when groupBy changes
  useEffect(() => {
    setGroupsInitialized(false);
  }, [groupBy]);

  // Toggle group expansion
  const toggleGroup = useCallback((groupKey: string) => {
    setExpandedGroups(prev => {
      const newSet = new Set(prev);
      if (newSet.has(groupKey)) {
        newSet.delete(groupKey);
      } else {
        newSet.add(groupKey);
      }
      return newSet;
    });
  }, []);

  // Get pagination info from backend
  const totalPages = useMemo(() => {
    // If pagination object exists, use totalPages from backend
    if (hookData?.pagination) {
      // Debug logging
      if (import.meta.env.DEV) {
        console.log('📊 FormGeneratorTable pagination data:', {
          totalPages: hookData.pagination.totalPages,
          totalItems: hookData.pagination.totalItems,
          currentPageSize,
          calculatedPages: hookData.pagination.totalItems && currentPageSize 
            ? Math.ceil(hookData.pagination.totalItems / currentPageSize) 
            : 'N/A'
        });
      }
      
      if (hookData.pagination.totalPages) {
        return hookData.pagination.totalPages;
      }
      // Fallback: calculate totalPages from totalItems and pageSize
      if (hookData.pagination.totalItems && currentPageSize) {
        const calculated = Math.ceil(hookData.pagination.totalItems / currentPageSize);
        return calculated;
      }
    }
    
    // Default to 1 page (pagination bar will still show)
    return 1;
  }, [hookData?.pagination, currentPageSize]);

  // Handle multi-column sorting
  // Click cycle: not sorted → ascending → descending → not sorted
  // When adding a new column, it becomes the last (lowest priority) sort
  const handleSort = (key: string) => {
    if (!sortable) return;
    
    setSortConfigs(current => {
      const existingIndex = current.findIndex(sc => sc.key === key);
      
      if (existingIndex === -1) {
        return [...current, { key, direction: 'asc' }];
      }
      
      const existing = current[existingIndex];
      if (existing.direction === 'asc') {
        const newConfigs = [...current];
        newConfigs[existingIndex] = { key, direction: 'desc' };
        return newConfigs;
      }
      
      return current.filter(sc => sc.key !== key);
    });
    setCurrentPage(1);
  };

  // Get sort info for a column (returns { direction, position } or null)
  const getSortInfo = useCallback((key: string): { direction: 'asc' | 'desc'; position: number } | null => {
    const index = sortConfigs.findIndex(sc => sc.key === key);
    if (index === -1) return null;
    return { direction: sortConfigs[index].direction, position: index + 1 };
  }, [sortConfigs]);

  // Handle filtering
  const handleFilter = (key: string, value: any, keepOpen = false) => {
    setFilters(prev => {
      const newFilters = { ...prev };
      if (value === undefined || value === '' || value === null) {
        delete newFilters[key];
      } else {
        newFilters[key] = value;
      }
      return newFilters;
    });
    setCurrentPage(1);
    if (!keepOpen) {
      setOpenFilterColumn(null);
    }
  };

  // Handle filter input focus
  const handleFilterFocus = (key: string, focused: boolean) => {
    setFilterFocused(prev => ({
      ...prev,
      [key]: focused
    }));
  };

  // Clear filter for a column
  const clearFilter = useCallback((key: string) => {
    setFilters(prev => {
      const newFilters = { ...prev };
      delete newFilters[key];
      return newFilters;
    });
    setCurrentPage(1);
    setOpenFilterColumn(null);
  }, []);

  // Count active filters
  const activeFiltersCount = useMemo(() => {
    return Object.keys(filters).filter(k => filters[k] !== undefined && filters[k] !== '').length;
  }, [filters]);

  // Track which filter columns show all values (expanded beyond initial 100)
  const [asyncFilterValues, setAsyncFilterValues] = useState<Record<string, string[]>>({});
  const [filterValuesLoading, setFilterValuesLoading] = useState<Record<string, boolean>>({});

  // Invalidate cached filter values when filters change (cross-filtering)
  const filtersRef = useRef(filters);
  useEffect(() => {
    if (filtersRef.current !== filters) {
      filtersRef.current = filters;
      setAsyncFilterValues({});
    }
  }, [filters]);

  // Load filter values on-demand when a filter dropdown is opened
  useEffect(() => {
    if (!openFilterColumn) return;

    const column = detectedColumns.find(c => c.key === openFilterColumn);

    // Skip if column has static filterOptions (enum) – those are used directly
    if (column?.filterOptions && column.filterOptions.length > 0) return;

    // FK columns: extract values from actual data instead of backend endpoint
    if (column?.fkSource) return;

    // Skip if already loaded or currently loading
    if (asyncFilterValues[openFilterColumn] || filterValuesLoading[openFilterColumn]) return;

    const _fetchValues = async (columnKey: string) => {
      setFilterValuesLoading(prev => ({ ...prev, [columnKey]: true }));
      try {
        let values: string[];
        if (hookData?.fetchFilterValues && typeof hookData.fetchFilterValues === 'function') {
          const crossFilters: Record<string, any> = {};
          Object.entries(filters).forEach(([k, v]) => {
            if (k !== columnKey && v !== undefined && v !== '') crossFilters[k] = v;
          });
          values = await hookData.fetchFilterValues(columnKey, crossFilters);
        } else if (apiEndpoint && supportsBackendPagination) {
          const endpoint = apiEndpoint.endsWith('/') ? apiEndpoint.slice(0, -1) : apiEndpoint;
          const params: Record<string, string> = { column: columnKey };
          if (Object.keys(filters).length > 0) {
            params.pagination = JSON.stringify({ filters });
          }
          const response = await api.get(`${endpoint}/filter-values`, { params });
          values = Array.isArray(response.data) ? response.data : [];
        } else {
          values = [];
        }
        setAsyncFilterValues(prev => ({ ...prev, [columnKey]: values }));
      } catch {
        setAsyncFilterValues(prev => ({ ...prev, [columnKey]: [] }));
      } finally {
        setFilterValuesLoading(prev => ({ ...prev, [columnKey]: false }));
      }
    };

    _fetchValues(openFilterColumn);
  }, [openFilterColumn, detectedColumns, asyncFilterValues, filterValuesLoading, hookData, apiEndpoint, supportsBackendPagination, filters]);

  // Get unique values for a column (for filter dropdown)
  // Sources: 1) column.filterOptions (static enum)
  //          2) asyncFilterValues (loaded from backend via hookData.fetchFilterValues)
  //          3) data — ONLY when no backend pagination (data = full dataset)
  // With backend pagination, data is a single page, so extracting filter
  // values from it would be incomplete and misleading.
  const getUniqueValuesForColumn = useCallback((columnKey: string): string[] => {
    const column = detectedColumns.find(c => c.key === columnKey);

    if (column?.filterOptions && column.filterOptions.length > 0) {
      return column.filterOptions;
    }

    // FK columns: extract distinct values from actual data (Excel autofilter style)
    if (column?.fkSource) {
      const seen = new Set<string>();
      data.forEach(row => {
        const val = row[columnKey];
        if (val && typeof val === 'string' && val.trim()) {
          seen.add(val);
        }
      });
      return Array.from(seen).sort((a, b) => {
        const labelA = fkCache[column.fkSource!]?.[a] || a;
        const labelB = fkCache[column.fkSource!]?.[b] || b;
        return labelA.localeCompare(labelB);
      });
    }

    if (asyncFilterValues[columnKey] && asyncFilterValues[columnKey].length > 0) {
      return asyncFilterValues[columnKey];
    }

    if (!apiEndpoint && !hookData?.fetchFilterValues) {
      console.warn(
        `FormGeneratorTable: Column "${columnKey}" is filterable ` +
        `but has no filterOptions, no hookData.fetchFilterValues, and no apiEndpoint. ` +
        `Filter dropdown will be empty. Provide apiEndpoint (auto-fetches /filter-values) ` +
        `or add filterOptions to the column config.`
      );
    }
    return [];
  }, [detectedColumns, asyncFilterValues, apiEndpoint, hookData, data, fkCache]);

  // Close filter dropdown when clicking outside
  useEffect(() => {
    const handleClickOutside = (event: MouseEvent) => {
      if (filterDropdownRef.current && !filterDropdownRef.current.contains(event.target as Node)) {
        setOpenFilterColumn(null);
      }
    };

    if (openFilterColumn) {
      document.addEventListener('mousedown', handleClickOutside);
      return () => document.removeEventListener('mousedown', handleClickOutside);
    }
  }, [openFilterColumn]);

  // Toggle filter dropdown
  const toggleFilterDropdown = useCallback((columnKey: string, event: React.MouseEvent) => {
    event.stopPropagation(); // Prevent sort from triggering
    setOpenFilterColumn(prev => prev === columnKey ? null : columnKey);
  }, []);

  // Handle row selection
  const handleRowSelect = (index: number) => {
    if (!selectable) return;
    
    const row = displayData[index];
    if (isRowSelectable && !isRowSelectable(row)) return;
    
    const newSelected = new Set(selectedRows);
    if (newSelected.has(index)) {
      newSelected.delete(index);
    } else {
      newSelected.add(index);
    }
    setSelectedRows(newSelected);
    
    if (onRowSelect) {
      const selectedData = Array.from(newSelected).map(i => displayData[i]);
      onRowSelect(selectedData);
    }
  };

  // Handle select all
  const handleSelectAll = () => {
    if (!selectable) return;
    
    // Get only selectable rows
    const selectableIndices = displayData
      .map((row, index) => ({ row, index }))
      .filter(({ row }) => !isRowSelectable || isRowSelectable(row))
      .map(({ index }) => index);
    
    if (selectedRows.size === selectableIndices.length) {
      setSelectedRows(new Set());
      onRowSelect?.([]);
    } else {
      const allSelectableIndices = new Set(selectableIndices);
      setSelectedRows(allSelectableIndices);
      const selectableData = selectableIndices.map(i => displayData[i]);
      onRowSelect?.(selectableData);
    }
  };

  // Handle delete single item
  const handleDeleteSingle = (row: T, index: number) => {
    if (onDelete) {
      onDelete(row);
      // Remove from selection if it was selected
      if (selectedRows.has(index)) {
        const newSelected = new Set(selectedRows);
        newSelected.delete(index);
        setSelectedRows(newSelected);
        if (onRowSelect) {
          const selectedData = Array.from(newSelected).map(i => displayData[i]);
          onRowSelect(selectedData);
        }
      }
    }
  };

  // Handle delete multiple items
  const handleDeleteMultiple = () => {
    if (onDeleteMultiple && selectedRows.size > 0) {
      const selectedData = Array.from(selectedRows).map(i => displayData[i]);
      onDeleteMultiple(selectedData);
      // Clear selection
      setSelectedRows(new Set());
      onRowSelect?.([]);
    }
  };

  // Handle page size change
  const handlePageSizeChange = (newPageSize: number) => {
    setCurrentPageSize(newPageSize);
    setCurrentPage(1); // Reset to first page when page size changes
  };

  // Handle column resizing - use refs to store stable handler references
  const handleMouseMoveRef = useRef<((e: MouseEvent) => void) | null>(null);
  const handleMouseUpRef = useRef<(() => void) | null>(null);
  // Track if we're resizing the actions column
  const resizingActionsColumn = useRef<boolean>(false);

  const handleMouseDown = (e: React.MouseEvent, columnKey: string) => {
    if (!resizable) return;
    
    e.preventDefault();
    e.stopPropagation(); // Prevent sorting when resizing
    
    resizingColumn.current = columnKey;
    startX.current = e.clientX;
    startWidth.current = columnWidths[columnKey] || 150;
    
    // Create stable handler functions and store in refs
    const mouseMoveHandler = (moveEvent: MouseEvent) => {
      if (!resizingColumn.current) return;
      
      const diff = moveEvent.clientX - startX.current;
      let newWidth = Math.max(100, startWidth.current + diff);
      
      // Prevent extending beyond table container
      const tableContainer = tableRef.current?.parentElement;
      if (tableContainer) {
        const cWidth = tableContainer.clientWidth;
        // Calculate actions column width dynamically: ~32px per button + padding
        const actionsColWidth = currentActionsWidth;
        const selectColumnWidth = selectable ? 40 : 0;
        const fixedWidth = actionsColWidth + selectColumnWidth;
        
        // Maximum allowed width - simple calculation to prevent overflow
        const maxAllowedWidth = cWidth - fixedWidth - 100; // Leave space for other columns
        newWidth = Math.min(newWidth, Math.max(100, maxAllowedWidth));
      }
      
      setColumnWidths(prev => ({
        ...prev,
        [resizingColumn.current!]: newWidth
      }));
    };
    
    const mouseUpHandler = () => {
      const resizedColumn = resizingColumn.current;
      resizingColumn.current = null;
      if (handleMouseMoveRef.current) {
        document.removeEventListener('mousemove', handleMouseMoveRef.current);
      }
      if (handleMouseUpRef.current) {
        document.removeEventListener('mouseup', handleMouseUpRef.current);
      }
      handleMouseMoveRef.current = null;
      handleMouseUpRef.current = null;
      
      // Save to localStorage after resize completes
      if (resizedColumn) {
        setColumnWidths(currentWidths => {
          saveColumnWidthsToStorage(currentWidths, storageKey);
          return currentWidths;
        });
      }
    };
    
    // Store handlers in refs for cleanup
    handleMouseMoveRef.current = mouseMoveHandler;
    handleMouseUpRef.current = mouseUpHandler;
    
    document.addEventListener('mousemove', mouseMoveHandler);
    document.addEventListener('mouseup', mouseUpHandler);
  };

  // Handle actions column resizing (separate handler)
  const handleActionsColumnMouseDown = (e: React.MouseEvent) => {
    if (!resizable) return;
    
    e.preventDefault();
    e.stopPropagation();
    
    resizingActionsColumn.current = true;
    startX.current = e.clientX;
    startWidth.current = currentActionsWidth;
    
    const mouseMoveHandler = (moveEvent: MouseEvent) => {
      if (!resizingActionsColumn.current) return;
      
      const diff = moveEvent.clientX - startX.current;
      // Minimum width: default width based on buttons, maximum: 40% of container
      const minWidth = defaultActionsWidth;
      const maxWidth = containerWidth > 0 ? containerWidth * 0.4 : 400;
      const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth.current + diff));
      
      setActionsColumnWidth(newWidth);
    };
    
    const mouseUpHandler = () => {
      resizingActionsColumn.current = false;
      if (handleMouseMoveRef.current) {
        document.removeEventListener('mousemove', handleMouseMoveRef.current);
      }
      if (handleMouseUpRef.current) {
        document.removeEventListener('mouseup', handleMouseUpRef.current);
      }
      handleMouseMoveRef.current = null;
      handleMouseUpRef.current = null;
    };
    
    handleMouseMoveRef.current = mouseMoveHandler;
    handleMouseUpRef.current = mouseUpHandler;
    
    document.addEventListener('mousemove', mouseMoveHandler);
    document.addEventListener('mouseup', mouseUpHandler);
  };
  
  // Cleanup on unmount
  useEffect(() => {
    return () => {
      if (handleMouseMoveRef.current) {
        document.removeEventListener('mousemove', handleMouseMoveRef.current);
      }
      if (handleMouseUpRef.current) {
        document.removeEventListener('mouseup', handleMouseUpRef.current);
      }
    };
  }, []);

  // Sync top scrollbar width with table width and handle scroll synchronization
  useEffect(() => {
    const tableContainer = tableContainerRef.current;
    const topScrollbar = topScrollbarRef.current;
    const topScrollbarInner = topScrollbarInnerRef.current;
    const table = tableRef.current;
    
    if (!tableContainer || !topScrollbar || !topScrollbarInner || !table) return;
    
    // Update top scrollbar inner width to match table width
    const updateScrollbarWidth = () => {
      const tableWidth = table.scrollWidth;
      topScrollbarInner.style.width = `${tableWidth}px`;
    };
    
    // Initial width calculation
    updateScrollbarWidth();
    
    // Observe table size changes
    const resizeObserver = new ResizeObserver(updateScrollbarWidth);
    resizeObserver.observe(table);
    
    // Sync scroll positions
    const syncTopToContainer = () => {
      if (isScrollingSyncRef.current) return;
      isScrollingSyncRef.current = true;
      tableContainer.scrollLeft = topScrollbar.scrollLeft;
      requestAnimationFrame(() => { isScrollingSyncRef.current = false; });
    };
    
    const syncContainerToTop = () => {
      if (isScrollingSyncRef.current) return;
      isScrollingSyncRef.current = true;
      topScrollbar.scrollLeft = tableContainer.scrollLeft;
      requestAnimationFrame(() => { isScrollingSyncRef.current = false; });
    };
    
    topScrollbar.addEventListener('scroll', syncTopToContainer);
    tableContainer.addEventListener('scroll', syncContainerToTop);
    
    return () => {
      resizeObserver.disconnect();
      topScrollbar.removeEventListener('scroll', syncTopToContainer);
      tableContainer.removeEventListener('scroll', syncContainerToTop);
    };
  }, [detectedColumns, columnWidths]); // ResizeObserver handles data-driven size changes

  // Track which cells are currently being updated (for loading state)
  const [updatingCells, setUpdatingCells] = useState<Set<string>>(new Set());

  // CSV Export state
  const [csvExporting, setCsvExporting] = useState(false);

  // CSV Export: fetch ALL data via direct API call (no state update, no table flicker)
  // Fetches in batches of 1000 (backend max pageSize) until all data is loaded.
  const handleCsvExport = useCallback(async () => {
    if (csvExporting || detectedColumns.length === 0 || !apiEndpoint) return;
    setCsvExporting(true);

    try {
      const batchSize = 1000; // Backend max pageSize
      let allData: T[] = [];
      let currentPage = 1;
      let hasMore = true;

      while (hasMore) {
        const response = await api.get(apiEndpoint, {
          params: { pagination: JSON.stringify({ page: currentPage, pageSize: batchSize }) }
        });

        let batchItems: T[];
        if (response.data && typeof response.data === 'object' && 'items' in response.data) {
          batchItems = Array.isArray(response.data.items) ? response.data.items : [];
          // Use pagination metadata to determine if more pages exist
          const totalPages = response.data.pagination?.totalPages || 1;
          hasMore = currentPage < totalPages;
        } else if (Array.isArray(response.data)) {
          batchItems = response.data;
          hasMore = false; // Non-paginated response — all data in one shot
        } else {
          batchItems = [];
          hasMore = false;
        }

        allData = allData.concat(batchItems);
        currentPage++;

        // Safety: stop after 100 pages (100k records)
        if (currentPage > 100) {
          hasMore = false;
        }
      }

      if (allData.length === 0) {
        setCsvExporting(false);
        return;
      }

      // Build CSV content
      const separator = ';';
      const _escapeCsvValue = (val: any): string => {
        if (val === null || val === undefined) return '';
        let str: string;
        if (typeof val === 'boolean') {
          str = val ? 'true' : 'false';
        } else if (typeof val === 'object') {
          str = _objectToDisplayString(val as Record<string, unknown>);
        } else {
          str = String(val);
        }
        // Escape quotes and wrap in quotes if needed
        if (str.includes(separator) || str.includes('"') || str.includes('\n') || str.includes('\r')) {
          str = '"' + str.replace(/"/g, '""') + '"';
        }
        return str;
      };

      // Header row
      const headerRow = detectedColumns.map(col => _escapeCsvValue(col.label)).join(separator);

      // Data rows
      const dataRows = allData.map(row => {
        return detectedColumns.map(col => {
          let cellValue = row[col.key];

          // FK resolution
          if (col.fkSource && typeof cellValue === 'string' && cellValue.length > 0) {
            const resolved = fkCache[col.fkSource]?.[cellValue];
            if (resolved) cellValue = resolved;
          }

          // Timestamp formatting
          const isTimestampField = /(at|date|time|timestamp|created|updated|expires|checked|valuta)$/i.test(col.key);
          const isExplicitDateType = col.type && isDateTimeType(col.type);
          if ((isTimestampField || isExplicitDateType) && typeof cellValue === 'number') {
            try {
              let ts = cellValue < 10000000000 ? cellValue : cellValue / 1000;
              const formatted = formatUnixTimestamp(ts, undefined, {
                year: 'numeric', month: '2-digit', day: '2-digit',
                hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false
              });
              cellValue = `${formatted.time} ${formatted.timezone}`;
            } catch { /* keep original */ }
          }

          return _escapeCsvValue(cellValue);
        }).join(separator);
      });

      const csvContent = '\ufeff' + [headerRow, ...dataRows].join('\r\n'); // BOM for Excel

      // Trigger download
      const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
      const url = URL.createObjectURL(blob);
      const link = document.createElement('a');
      link.href = url;
      link.download = `export_${new Date().toISOString().slice(0, 10)}.csv`;
      document.body.appendChild(link);
      link.click();
      document.body.removeChild(link);
      URL.revokeObjectURL(url);
    } catch (error) {
      console.error('CSV export failed:', error);
    } finally {
      setCsvExporting(false);
    }
  }, [csvExporting, detectedColumns, apiEndpoint, currentLanguage, fkCache]);

  // Check if inline editing is allowed for a column (based on RBAC permissions)
  const canInlineEdit = useMemo(() => {
    // Auto-enable if inlineEditable is explicitly true OR if handleInlineUpdate is available
    const hasInlineSupport = inlineEditable || hookData?.handleInlineUpdate || onInlineUpdate;
    if (!hasInlineSupport) return false;
    // Check RBAC permissions - need update permission
    const permissions = hookData?.permissions;
    if (!permissions) return false;
    // Permission 'n' means no access, anything else (a, m, g) means allowed
    return permissions.update !== 'n' && permissions.view;
  }, [inlineEditable, hookData?.handleInlineUpdate, hookData?.permissions, onInlineUpdate]);

  // Check if a column supports inline editing (only checkbox/boolean types)
  const isInlineEditableColumn = useCallback((column: ColumnConfig): boolean => {
    if (!column.type) return false;
    return isCheckboxType(column.type);
  }, []);

  // Handle inline toggle for boolean fields
  const handleInlineToggle = useCallback(async (row: T, column: ColumnConfig, currentValue: boolean) => {
    if (!canInlineEdit || !isInlineEditableColumn(column)) return;
    
    const rowId = row[idField];
    const cellKey = `${rowId}-${column.key}`;
    
    // Check if update function is available (either from prop or hookData)
    const updateFn = onInlineUpdate || hookData?.handleInlineUpdate;
    if (!updateFn) {
      // Silent return - inline editing is optional, no warning needed
      return;
    }
    
    // Mark cell as updating
    setUpdatingCells(prev => new Set(prev).add(cellKey));
    
    const newValue = !currentValue;
    const hasOptimisticUpdate = !!hookData?.updateOptimistically;
    
    // If updateOptimistically is available, use it for immediate UI feedback
    if (hasOptimisticUpdate) {
      hookData.updateOptimistically(rowId, { [column.key]: newValue });
    }
    
    try {
      // Call the update function (generic - no entity-specific logic)
      if (onInlineUpdate) {
        await onInlineUpdate(row, column.key, newValue);
      } else if (hookData?.handleInlineUpdate) {
        // Pass row as third parameter for hooks that need to merge changes with existing data
        await hookData.handleInlineUpdate(rowId, { [column.key]: newValue }, row);
      }
      
      // Only refetch if we DON'T have optimistic update (to get fresh data)
      // With optimistic update, local state is already correct
      if (!hasOptimisticUpdate && hookData?.refetch) {
        await hookData.refetch();
      }
    } catch (error) {
      console.error('FormGeneratorTable: Inline update failed:', error);
      // Revert optimistic update on error
      if (hasOptimisticUpdate) {
        hookData.updateOptimistically(rowId, { [column.key]: currentValue });
      }
      // Refetch to restore consistent state on error
      if (hookData?.refetch) {
        await hookData.refetch();
      }
    } finally {
      // Remove cell from updating state
      setUpdatingCells(prev => {
        const newSet = new Set(prev);
        newSet.delete(cellKey);
        return newSet;
      });
    }
  }, [canInlineEdit, isInlineEditableColumn, idField, onInlineUpdate, hookData]);

  // Render inline-editable boolean cell
  const renderBooleanCell = useCallback((value: any, column: ColumnConfig, row: T): React.ReactNode => {
    const boolValue = Boolean(value);
    const rowId = row[idField];
    const cellKey = `${rowId}-${column.key}`;
    const isUpdating = updatingCells.has(cellKey) || inlineEditingRows?.has(rowId);
    const isEditable = canInlineEdit && isInlineEditableColumn(column);
    
    // Loading spinner for updating state
    if (isUpdating) {
      return (
        <span className={styles.booleanCell}>
          <span className={styles.booleanLoading}>⋯</span>
        </span>
      );
    }
    
    // Clickable or static based on permissions
    if (isEditable) {
      return (
        <button
          type="button"
          className={`${styles.booleanCell} ${styles.booleanEditable} ${boolValue ? styles.booleanTrue : styles.booleanFalse}`}
          onClick={(e) => {
            e.stopPropagation(); // Prevent row click
            handleInlineToggle(row, column, boolValue);
          }}
          title={t('Zum Ein-/Ausklappen klicken')}
        >
          {boolValue ? '✓' : '○'}
        </button>
      );
    }
    
    // Non-editable display
    return (
      <span className={`${styles.booleanCell} ${styles.booleanReadonly} ${boolValue ? styles.booleanTrue : styles.booleanFalse}`}>
        {boolValue ? '✓' : '○'}
      </span>
    );
  }, [idField, updatingCells, inlineEditingRows, canInlineEdit, isInlineEditableColumn, handleInlineToggle, t]);

  // Check if a column is an ID field (generic logic only)
  // Matches: "id", "ID", "id" followed by uppercase (e.g., "idUser"), ends with "Id" or "ID" (e.g., "userId", "userID")
  const isIdField = (columnKey: string): boolean => {
    return /^(id|ID|_id)$/.test(columnKey) ||  // Exact match: "id", "ID", "_id"
           /^id[A-Z]/.test(columnKey) ||        // Starts with "id" + uppercase: "idUser", "idMandate"
           /Id$/.test(columnKey) ||             // Ends with "Id": "userId", "mandateId"
           /ID$/.test(columnKey);               // Ends with "ID": "userID", "mandateID"
  };

  // Check if a column is a hash field
  const isHashField = (columnKey: string, value: any): boolean => {
    const hashPatterns = /hash|Hash|HASH/i;
    if (hashPatterns.test(columnKey)) {
      return true;
    }
    // Also check if value looks like a hash (long alphanumeric string)
    if (typeof value === 'string' && value.length > 20) {
      const hashLikePattern = /^[a-f0-9]{20,}$/i;
      return hashLikePattern.test(value);
    }
    return false;
  };

  // Format cell value
  const formatCellValue = (value: any, column: ColumnConfig, row: T) => {
    // Custom formatter must run even when value is null/undefined (e.g. synthetic columns like _documentRefs)
    if (column.formatter) {
      return column.formatter(value, row);
    }

    if (value === null || value === undefined) {
      return '-';
    }

    // Handle boolean/checkbox fields with inline editing support
    if (column.type && isCheckboxType(column.type)) {
      return renderBooleanCell(value, column, row);
    }
    
    // Also detect boolean values even if column type isn't explicitly set
    if (typeof value === 'boolean') {
      return renderBooleanCell(value, column, row);
    }

    // FK Resolution: If column has fkSource and value is a string (UUID), resolve to display label
    if (column.fkSource && typeof value === 'string' && value.length > 0) {
      const resolvedLabel = resolveFkValue(value, column.fkSource);
      const isLoading = fkLoading[column.fkSource];
      
      // Show loading indicator or resolved label
      if (isLoading && !fkCache[column.fkSource]?.[value]) {
        return <span className={styles.fkLoading}>{value.substring(0, 8)}...</span>;
      }
      
      return resolvedLabel;
    }

    // Check if this is an ID or hash field that should be truncated and copyable
    // Do this BEFORE checking for custom formatters to ensure IDs/hashes are always copyable
    const isId = isIdField(column.key);
    const isHash = isHashField(column.key, value);
    
    if ((isId || isHash)) {
      // Convert to string if needed
      const stringValue = String(value);
      if (stringValue.length > 0) {
        return <CopyableTruncatedValue value={stringValue} maxLength={isHash ? 12 : 20} />;
      }
    }

    // Handle objects (e.g., references to other entities, or unresolved TextMultilingual)
    if (typeof value === 'object' && value !== null && !(value instanceof Date) && !Array.isArray(value)) {
      return _objectToDisplayString(value as Record<string, unknown>);
    }

    // Handle arrays (e.g., multiselect values)
    if (Array.isArray(value)) {
      if (value.length === 0) {
        return '-';
      }
      // If array contains objects, try to extract display values
      const displayValues = value.map(item => {
        if (typeof item === 'object' && item !== null) {
          // Try to find a display field
          const displayFields = ['label', 'name', 'title', 'id', 'value', 'text'];
          for (const field of displayFields) {
            if (item[field] !== undefined && item[field] !== null) {
              return String(item[field]);
            }
          }
          // Fallback to stringified object
          try {
            return JSON.stringify(item);
          } catch {
            return String(item);
          }
        }
        return String(item);
      });
      return displayValues.join(', ');
    }

    // Check if this is a timestamp field based on name OR explicit type
    // Do NOT treat arbitrary numbers as timestamps - only if field name suggests it
    const isTimestampField = /(at|date|time|timestamp|created|updated|expires|checked|valuta)$/i.test(column.key);
    const isExplicitDateType = column.type && isDateTimeType(column.type);
    
    // Only format as date if: field name suggests timestamp OR explicit date type
    // Do NOT format based on value range alone - this causes amounts/percentages to show as dates
    if ((isTimestampField || isExplicitDateType) && typeof value === 'number') {
      try {
        // Handle Unix timestamps in seconds (backend format)
        let timestamp: number;
        if (value < 10000000000) {
          // Likely Unix timestamp in seconds
          timestamp = value;
        } else {
          // Likely Unix timestamp in milliseconds
          timestamp = value / 1000;
        }
        
        const formatted = formatUnixTimestamp(timestamp, undefined, {
          year: 'numeric',
          month: '2-digit',
          day: '2-digit',
          hour: '2-digit',
          minute: '2-digit',
          second: '2-digit',
          hour12: false
        });
        
        return `${formatted.time} ${formatted.timezone}`;
      } catch (error) {
        console.error('Error formatting timestamp:', error, value);
        // Fall through to default number formatting
      }
    }
    
    switch (column.type) {
      case 'date':
      case 'timestamp':
      case 'time':
        try {
          // Handle Unix timestamps in seconds (backend format)
          let timestamp: number;
          
          if (typeof value === 'number') {
            // If it's a number, check if it's in seconds (typical Unix timestamp range)
            // Unix timestamps in seconds are typically much smaller than milliseconds
            if (value < 10000000000) { // Less than year 2286 in seconds
              timestamp = value; // Already in seconds
            } else {
              timestamp = value / 1000; // Convert milliseconds to seconds
            }
          } else if (typeof value === 'string') {
            // Check if it's an ISO date string or date string (contains 'T', '-', or ':')
            if (value.includes('T') || value.includes('-') || value.includes(':')) {
              // Parse as date string and convert to Unix timestamp in seconds
              const date = new Date(value);
              if (isNaN(date.getTime())) return '-';
              timestamp = Math.floor(date.getTime() / 1000);
            } else {
              // Try to parse as number (Unix timestamp as string)
              const numValue = parseFloat(value);
              if (!isNaN(numValue)) {
                if (numValue < 10000000000) { // Less than year 2286 in seconds
                  timestamp = numValue; // Already in seconds
                } else {
                  timestamp = numValue / 1000; // Convert milliseconds to seconds
                }
              } else {
                // Fallback: try parsing as date string
                const date = new Date(value);
                if (isNaN(date.getTime())) return '-';
                timestamp = Math.floor(date.getTime() / 1000);
              }
            }
          } else if (value instanceof Date) {
            // Already a Date object
            if (isNaN(value.getTime())) return '-';
            timestamp = Math.floor(value.getTime() / 1000);
          } else {
            // Try to convert to Date and then to timestamp
            const date = new Date(value);
            if (isNaN(date.getTime())) return '-';
            timestamp = Math.floor(date.getTime() / 1000);
          }
          
          // Use formatUnixTimestamp utility function
          const formatted = formatUnixTimestamp(timestamp, undefined, {
            year: 'numeric',
            month: '2-digit',
            day: '2-digit',
            hour: '2-digit',
            minute: '2-digit',
            second: '2-digit',
            hour12: false
          });
          
          return `${formatted.time} ${formatted.timezone}`;
        } catch (error) {
          console.error('Error formatting date:', error, value);
          return '-';
        }
      case 'boolean':
        return value ? '✓' : '✗';
      case 'number':
        return typeof value === 'number' ? value.toLocaleString() : value;
      default:
        return String(value);
    }
  };

  return (
    <div className={`${styles.formGeneratorTable} ${className}`}>
      
      {(searchable || (selectable && selectedRows.size > 0)) && (
        <FormGeneratorControls
          fields={detectedColumns}
          searchTerm={searchTerm}
          onSearchChange={setSearchTerm}
          searchFocused={searchFocused}
          onSearchFocus={setSearchFocused}
          filters={filters}
          onFilterChange={handleFilter}
          filterFocused={filterFocused}
          onFilterFocus={handleFilterFocus}
          selectedCount={selectedRows.size}
          displayData={displayData}
          onDeleteSingle={selectedRows.size === 1 && onDelete ? () => {
            const selectedIndex = Array.from(selectedRows)[0];
            const selectedRow = displayData[selectedIndex];
            handleDeleteSingle(selectedRow, selectedIndex);
          } : undefined}
          onDeleteMultiple={(() => {
            if (!onDeleteMultiple) return undefined;
            // Show delete multiple button if:
            // 1. More than 1 item is selected, OR
            // 2. All selectable items are selected (even if it's just 1)
            const selectableIndices = displayData
              .map((row, index) => ({ row, index }))
              .filter(({ row }) => !isRowSelectable || isRowSelectable(row))
              .map(({ index }) => index);
            const allSelected = selectedRows.size === selectableIndices.length && selectableIndices.length > 0;
            return (selectedRows.size > 1 || allSelected) ? handleDeleteMultiple : undefined;
          })()}
          batchActions={batchActions.length > 0 ? batchActions.map((ba) => ({
            label: ba.label,
            icon: ba.icon,
            loading: ba.loading,
            onClick: async () => {
              const rows = Array.from(selectedRows).map((i) => displayData[i]);
              try {
                await Promise.resolve(ba.onClick(rows));
                setSelectedRows(new Set());
                onRowSelect?.([]);
              } catch {
                // Keep selection on error so user can retry
              }
            },
          })) : undefined}
          onRefresh={onRefresh}
          searchable={searchable}
          selectable={selectable}
          loading={loading}
          activeFiltersCount={activeFiltersCount}
          pagination={pagination}
          currentPage={currentPage}
          totalPages={totalPages}
          currentPageSize={currentPageSize}
          pageSizeOptions={pageSizeOptions}
          showPageSizeSelector={showPageSizeSelector}
          onPageChange={setCurrentPage}
          onPageSizeChange={handlePageSizeChange}
          supportsBackendPagination={supportsBackendPagination}
          hookData={hookData}
          onCsvExport={apiEndpoint ? handleCsvExport : undefined}
          csvExporting={csvExporting}
        />
      )}

      {/* Table Wrapper - contains top scrollbar and table container */}
      <div className={`${styles.tableWrapper} ${displayData.length === 0 && !loading ? styles.emptyTable : ''}`}>
        {/* Top horizontal scrollbar - syncs with table container */}
        <div 
          ref={topScrollbarRef}
          className={styles.topScrollbar}
        >
          <div ref={topScrollbarInnerRef} className={styles.topScrollbarInner} />
        </div>
        
        {/* Table Container - vertical scroll only */}
        <div 
          ref={tableContainerRef}
          className={styles.tableContainer}
        >
          {/* Loading overlay - shown while loading */}
          {loading && (
            <div className={styles.loadingOverlay}>
              <div className={styles.loadingSpinner}></div>
              <p>{t('Laden...')}</p>
            </div>
          )}
          
          <table ref={tableRef} className={styles.table}>
            <thead>
              <tr>
                {selectable && (
                  <th className={styles.selectColumn} style={{ width: '40px', minWidth: '40px', maxWidth: '40px' }}>
                    <input
                      type="checkbox"
                      checked={(() => {
                        const selectableIndices = displayData
                          .map((row, index) => ({ row, index }))
                          .filter(({ row }) => !isRowSelectable || isRowSelectable(row))
                          .map(({ index }) => index);
                        return selectedRows.size === selectableIndices.length && selectableIndices.length > 0;
                      })()}
                      onChange={handleSelectAll}
                      title={t('Alle Elemente auswählen')}
                    />
                  </th>
                )}
                {hasActionColumn && (
                  <th 
                    className={styles.actionsColumn}
                    style={{ 
                      width: `${currentActionsWidth}px`, 
                      minWidth: `${defaultActionsWidth}px`,
                      position: 'relative'
                    }}
                  >
                    {resizable && (
                      <div
                        className={styles.resizeHandle}
                        onMouseDown={handleActionsColumnMouseDown}
                      />
                    )}
                  </th>
                )}
                {detectedColumns.map(column => (
                  <th
                    key={column.key}
                    className={`${styles.th} ${sortable && column.sortable ? styles.sortable : ''}`}
                    style={{ 
                      width: columnWidths[column.key] || column.width || 150,
                      minWidth: columnWidths[column.key] || column.width || 150,
                      maxWidth: columnWidths[column.key] || column.width || 150,
                      position: 'relative'
                    }}
                  >
                    <div className={styles.headerContent}>
                      {/* Filter icon */}
                      {filterable && column.filterable !== false && (
                        <button
                          className={`${styles.filterIcon} ${filters[column.key] ? styles.filterActive : ''}`}
                          onClick={(e) => toggleFilterDropdown(column.key, e)}
                          title={filters[column.key] 
                            ? t('Filter: {value}', { value: String(filters[column.key]) })
                            : t('Zum Filtern klicken')
                          }
                        >
                          <FaFilter size={10} />
                        </button>
                      )}
                      {/* Sort icon */}
                      {sortable && column.sortable && (() => {
                        const sortInfo = getSortInfo(column.key);
                        return (
                          <span 
                            className={`${styles.sortIcon} ${sortInfo ? styles.sortActive : ''}`}
                            onClick={() => handleSort(column.key)}
                            title={sortInfo 
                              ? t('Sortierung {position}: {direction}', {
                                    position: String(sortInfo.position),
                                    direction:
                                        sortInfo.direction === 'asc' ? 'aufsteigend' : 'absteigend',
                                })
                              : t('Zum Sortieren klicken')
                            }
                          >
                            {sortInfo ? (
                              <>
                                {sortInfo.direction === 'asc' ? '↑' : '↓'}
                                {sortConfigs.length > 1 && <sub>{sortInfo.position}</sub>}
                              </>
                            ) : '↕'}
                          </span>
                        );
                      })()}
                      {/* Column label */}
                      <span 
                        className={styles.columnLabel}
                        onClick={() => column.sortable && handleSort(column.key)}
                      >
                        {column.label}
                      </span>
                    </div>
                    
                    {/* Filter dropdown */}
                    {openFilterColumn === column.key && (
                      <div 
                        ref={filterDropdownRef}
                        className={styles.filterDropdown}
                        onClick={(e) => e.stopPropagation()}
                      >
                        <div className={styles.filterDropdownHeader}>
                          <span>{t('Filter')}: {column.label}</span>
                          {filters[column.key] && (
                            <button
                              className={styles.filterClearBtn}
                              onClick={() => clearFilter(column.key)}
                              title={t('Filter löschen')}
                            >
                              ✕
                            </button>
                          )}
                        </div>
                        <div className={styles.filterDropdownOptions}>
                          {(() => {
                            const colType = column.type || 'text';
                            const isBool = isCheckboxType(colType as AttributeType);
                            const isDate = isDateTimeType(colType as AttributeType);

                            if (isBool) {
                              const currentVal = filters[column.key];
                              return (
                                <>
                                  <div
                                    className={`${styles.filterOption} ${!currentVal ? styles.filterOptionSelected : ''}`}
                                    onClick={() => clearFilter(column.key)}
                                  >
                                    ({t('Alle')})
                                  </div>
                                  <div
                                    className={`${styles.filterOption} ${currentVal === 'true' ? styles.filterOptionSelected : ''}`}
                                    onClick={() => handleFilter(column.key, 'true')}
                                  >
                                    {t('Ja')}
                                  </div>
                                  <div
                                    className={`${styles.filterOption} ${currentVal === 'false' ? styles.filterOptionSelected : ''}`}
                                    onClick={() => handleFilter(column.key, 'false')}
                                  >
                                    {t('Nein')}
                                  </div>
                                </>
                              );
                            }

                            if (isDate) {
                              const rangeVal = (typeof filters[column.key] === 'object' && filters[column.key]?.value) || {};
                              return (
                                <div style={{ padding: '6px 8px', display: 'flex', flexDirection: 'column', gap: '6px' }}>
                                  <div
                                    className={`${styles.filterOption} ${!filters[column.key] ? styles.filterOptionSelected : ''}`}
                                    onClick={() => clearFilter(column.key)}
                                  >
                                    ({t('Alle')})
                                  </div>
                                  <label style={{ fontSize: '11px', color: 'var(--text-muted, #64748b)' }}>
                                    {t('Von')}
                                  </label>
                                  <input
                                    type="date"
                                    value={rangeVal.from || ''}
                                    style={{ width: '100%', padding: '4px 6px', fontSize: '12px', border: '1px solid var(--color-border, #ddd)', borderRadius: '4px' }}
                                    onChange={(e) => {
                                      const from = e.target.value;
                                      const to = rangeVal.to || '';
                                      if (!from && !to) {
                                        clearFilter(column.key);
                                      } else {
                                        handleFilter(column.key, { operator: 'between', value: { from, to } }, true);
                                      }
                                    }}
                                  />
                                  <label style={{ fontSize: '11px', color: 'var(--text-muted, #64748b)' }}>
                                    {t('Bis')}
                                  </label>
                                  <input
                                    type="date"
                                    value={rangeVal.to || ''}
                                    style={{ width: '100%', padding: '4px 6px', fontSize: '12px', border: '1px solid var(--color-border, #ddd)', borderRadius: '4px' }}
                                    onChange={(e) => {
                                      const to = e.target.value;
                                      const from = rangeVal.from || '';
                                      if (!from && !to) {
                                        clearFilter(column.key);
                                      } else {
                                        handleFilter(column.key, { operator: 'between', value: { from, to } }, true);
                                      }
                                    }}
                                  />
                                </div>
                              );
                            }

                            return (
                              <>
                                <div
                                  className={`${styles.filterOption} ${!filters[column.key] ? styles.filterOptionSelected : ''}`}
                                  onClick={() => clearFilter(column.key)}
                                >
                                  ({t('Alle')})
                                </div>
                                {filterValuesLoading[column.key] ? (
                                  <div className={styles.filterOptionMore} style={{ textAlign: 'center', padding: '8px' }}>
                                    {t('Lade Filterwerte...')}
                                  </div>
                                ) : (
                                  <FilterValuesList
                                    columnKey={column.key}
                                    allValues={getUniqueValuesForColumn(column.key)}
                                    activeFilter={filters[column.key]}
                                    onSelect={(value) => handleFilter(column.key, value)}
                                    resolveLabel={column.fkSource ? (val) => fkCache[column.fkSource!]?.[val] || val : undefined}
                                  />
                                )}
                              </>
                            );
                          })()}
                        </div>
                      </div>
                    )}
                    
                    {resizable && (
                      <div
                        className={styles.resizeHandle}
                        onMouseDown={(e) => handleMouseDown(e, column.key)}
                      />
                    )}
                  </th>
                ))}
              </tr>
            </thead>
              <tbody>
                {groupBy && groupedData ? (
                  Array.from(groupedData.entries()).map(([groupKey, groupRows]) => {
                    const isExpanded = expandedGroups.has(groupKey);
                    const cellData = groupRowData ? groupRowData(groupKey, groupRows) : null;
                    
                    return (
                      <React.Fragment key={`group-${groupKey}`}>
                        <tr 
                          className={`${styles.tr} ${styles.groupHeader}`}
                          onClick={() => toggleGroup(groupKey)}
                          style={{ 
                            cursor: 'pointer', 
                            backgroundColor: 'var(--bg-secondary, #f8fafc)',
                            borderBottom: '2px solid var(--border-color, #e2e8f0)'
                          }}
                        >
                          {selectable && (
                            <td className={styles.selectColumn} style={{ width: '40px', minWidth: '40px', maxWidth: '40px', textAlign: 'center' }}>
                              <span style={{ 
                                display: 'inline-flex', fontSize: '11px', transition: 'transform 0.2s',
                                transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
                                color: 'var(--text-muted, #64748b)',
                              }}>▶</span>
                            </td>
                          )}
                          {hasActionColumn && (
                            <td 
                              className={styles.actionsColumn}
                              style={{ width: `${currentActionsWidth}px`, minWidth: `${defaultActionsWidth}px` }}
                            >
                              {!selectable && (
                                <span style={{ 
                                  display: 'inline-flex', fontSize: '11px', transition: 'transform 0.2s',
                                  transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
                                  color: 'var(--text-muted, #64748b)', marginRight: '6px',
                                }}>▶</span>
                              )}
                              {groupActions && (
                                <span onClick={(e) => e.stopPropagation()}>
                                  {groupActions(groupKey, groupRows)}
                                </span>
                              )}
                            </td>
                          )}
                          {detectedColumns.map((column, colIdx) => {
                            const cellContent = cellData?.[column.key];
                            const hasContent = cellContent !== undefined && cellContent !== null;
                            const showExpandIcon = !selectable && actionButtons.length === 0 && colIdx === 0;
                            
                            return (
                              <td 
                                key={column.key} 
                                className={styles.td}
                                style={{ 
                                  width: columnWidths[column.key] || column.width || 150,
                                  minWidth: columnWidths[column.key] || column.width || 150,
                                  maxWidth: columnWidths[column.key] || column.width || 150,
                                  fontWeight: hasContent ? 600 : 'normal',
                                }}
                              >
                                {showExpandIcon && (
                                  <span style={{ 
                                    display: 'inline-flex', fontSize: '11px', transition: 'transform 0.2s',
                                    transform: isExpanded ? 'rotate(90deg)' : 'rotate(0deg)',
                                    color: 'var(--text-muted, #64748b)', marginRight: '6px',
                                  }}>▶</span>
                                )}
                                {hasContent ? cellContent : ''}
                              </td>
                            );
                          })}
                        </tr>
                        {isExpanded && groupRows.map((row, rowIndex) => {
                          const globalIndex = displayData.indexOf(row);
                          const dataAttributes = getRowDataAttributes ? getRowDataAttributes(row, globalIndex) : {};
                          return (
                            <tr
                              key={`${groupKey}-row-${rowIndex}`}
                              className={`${styles.tr} ${selectedRows.has(globalIndex) ? styles.selected : ''} ${onRowClick ? styles.clickable : ''}`}
                              onClick={() => onRowClick?.(row, globalIndex)}
                              draggable={rowDraggable}
                              onDragStart={rowDraggable && onRowDragStart ? (e) => onRowDragStart(e, row) : undefined}
                              {...Object.fromEntries(
                                Object.entries(dataAttributes).map(([key, value]) => [`data-${key}`, value])
                              )}
                            >
                              {selectable && (
                                <td className={styles.selectColumn} style={{ width: '40px', minWidth: '40px', maxWidth: '40px' }}>
                                  <input
                                    type="checkbox"
                                    checked={selectedRows.has(globalIndex)}
                                    onChange={() => handleRowSelect(globalIndex)}
                                    onClick={(e) => e.stopPropagation()}
                                    disabled={isRowSelectable && !isRowSelectable(row)}
                                    title={
                                      isRowSelectable && !isRowSelectable(row) 
                                        ? t('Dieses Element kann nicht ausgewählt werden')
                                        : t('Dieses Element auswählen')
                                    }
                                    style={{
                                      opacity: isRowSelectable && !isRowSelectable(row) ? 0.4 : 1,
                                      cursor: isRowSelectable && !isRowSelectable(row) ? 'not-allowed' : 'pointer'
                                    }}
                                  />
                                </td>
                              )}
                              {hasActionColumn && (
                                <td 
                                  className={styles.actionsColumn}
                                  style={{ 
                                    width: `${currentActionsWidth}px`, 
                                    minWidth: `${defaultActionsWidth}px`
                                  }}
                                >
                                  <div 
                                    ref={(el) => {
                                      if (el) {
                                        actionButtonsRefs.current.set(globalIndex, el);
                                      } else {
                                        actionButtonsRefs.current.delete(globalIndex);
                                      }
                                    }}
                                    className={`${styles.actionButtons} ${shouldWrapActionButtons ? styles.actionButtonsWrap : ''}`}
                                  >
                                    {actionButtons.map((actionButton, actionIndex) => {
                                      const actionTitle = typeof actionButton.title === 'function' 
                                        ? actionButton.title(row) 
                                        : actionButton.title;
                                      let disabledResult: boolean | { disabled: boolean; message?: string } = false;
                                      if (actionButton.disabled) {
                                        disabledResult = actionButton.disabled(row, hookData);
                                      } else if (row._permissions) {
                                        if (actionButton.type === 'edit' && row._permissions.canUpdate === false) {
                                          disabledResult = true;
                                        } else if (actionButton.type === 'delete' && row._permissions.canDelete === false) {
                                          disabledResult = true;
                                        }
                                      }
                                      const isLoading = actionButton.loading ? actionButton.loading(row) : false;
                                      const isProcessing = actionButton.isProcessing ? actionButton.isProcessing(row) : false;
                                      const baseProps = {
                                        row, disabled: disabledResult, loading: isLoading,
                                        className: actionButton.className, title: actionTitle,
                                        idField: actionButton.idField ?? 'id', nameField: actionButton.nameField ?? 'name',
                                        typeField: actionButton.typeField ?? 'type', contentField: actionButton.contentField ?? 'content',
                                        operationName: actionButton.operationName, loadingStateName: actionButton.loadingStateName
                                      };
                                      switch (actionButton.type) {
                                        case 'edit':
                                          return <EditActionButton key={`action-${actionIndex}`} {...baseProps} onEdit={actionButton.onAction} hookData={hookData} />;
                                        case 'delete':
                                          return <DeleteActionButton key={`action-${actionIndex}`} {...baseProps} containerRef={{ current: actionButtonsRefs.current.get(globalIndex) || null }} hookData={hookData} />;
                                        case 'view':
                                          return <ViewActionButton key={`action-${actionIndex}`} {...baseProps} onView={actionButton.onAction || (() => {})} isViewing={isProcessing} hookData={hookData} />;
                                        case 'copy':
                                          return <CopyActionButton key={`action-${actionIndex}`} {...baseProps} onCopy={actionButton.onAction} isCopying={isProcessing} contentField={actionButton.contentField} />;
                                        default:
                                          return null;
                                      }
                                    })}
                                    {customActions.map((customAction) => (
                                      <CustomActionButton key={`custom-${customAction.id}`} row={row} id={customAction.id} icon={customAction.icon}
                                        onClick={customAction.onClick} visible={customAction.visible} disabled={customAction.disabled}
                                        loading={customAction.loading} title={customAction.title} className={customAction.className}
                                        hookData={hookData} idField={customAction.idField ?? 'id'} />
                                    ))}
                                  </div>
                                </td>
                              )}
                              {detectedColumns.map(column => {
                                const cellValue = row[column.key];
                                const customClassName = column.cellClassName ? column.cellClassName(cellValue, row) : '';
                                const combinedClassName = `${styles.td} ${customClassName}`.trim();
                                const isNumeric = column.type === 'number' || column.type === 'float' || column.type === 'integer';
                                return (
                                  <td key={column.key} className={combinedClassName}
                                    style={{ width: columnWidths[column.key] || column.width || 150, minWidth: columnWidths[column.key] || column.width || 150, maxWidth: columnWidths[column.key] || column.width || 150, ...(isNumeric ? { textAlign: 'right' } : {}) }}>
                                    {formatCellValue(cellValue, column, row)}
                                  </td>
                                );
                              })}
                            </tr>
                          );
                        })}
                      </React.Fragment>
                    );
                  })
                ) : (
                  displayData.map((row, index) => {
                    const dataAttributes = getRowDataAttributes ? getRowDataAttributes(row, index) : {};
                    return (
                      <tr
                        key={index}
                        className={`${styles.tr} ${selectedRows.has(index) ? styles.selected : ''} ${onRowClick ? styles.clickable : ''}`}
                        onClick={() => onRowClick?.(row, index)}
                        draggable={rowDraggable}
                        onDragStart={rowDraggable && onRowDragStart ? (e) => onRowDragStart(e, row) : undefined}
                        {...Object.fromEntries(
                          Object.entries(dataAttributes).map(([key, value]) => [`data-${key}`, value])
                        )}
                      >
                        {selectable && (
                          <td className={styles.selectColumn} style={{ width: '40px', minWidth: '40px', maxWidth: '40px' }}>
                            <input
                              type="checkbox"
                              checked={selectedRows.has(index)}
                              onChange={() => handleRowSelect(index)}
                              onClick={(e) => e.stopPropagation()}
                              disabled={isRowSelectable && !isRowSelectable(row)}
                              title={
                                isRowSelectable && !isRowSelectable(row) 
                                  ? t('Dieses Element kann nicht ausgewählt werden')
                                  : t('Dieses Element auswählen')
                              }
                              style={{
                                opacity: isRowSelectable && !isRowSelectable(row) ? 0.4 : 1,
                                cursor: isRowSelectable && !isRowSelectable(row) ? 'not-allowed' : 'pointer'
                              }}
                            />
                          </td>
                        )}
                        {hasActionColumn && (
                          <td 
                            className={styles.actionsColumn}
                            style={{ 
                              width: `${currentActionsWidth}px`, 
                              minWidth: `${defaultActionsWidth}px`
                            }}
                          >
                            <div 
                              ref={(el) => {
                                if (el) {
                                  actionButtonsRefs.current.set(index, el);
                                } else {
                                  actionButtonsRefs.current.delete(index);
                                }
                              }}
                              className={`${styles.actionButtons} ${shouldWrapActionButtons ? styles.actionButtonsWrap : ''}`}
                            >
                              {actionButtons.map((actionButton, actionIndex) => {
                                const actionTitle = typeof actionButton.title === 'function' 
                                  ? actionButton.title(row) 
                                  : actionButton.title;
                                let disabledResult: boolean | { disabled: boolean; message?: string } = false;
                                if (actionButton.disabled) {
                                  disabledResult = actionButton.disabled(row, hookData);
                                } else if (row._permissions) {
                                  if (actionButton.type === 'edit' && row._permissions.canUpdate === false) {
                                    disabledResult = true;
                                  } else if (actionButton.type === 'delete' && row._permissions.canDelete === false) {
                                    disabledResult = true;
                                  }
                                }
                                const isLoading = actionButton.loading ? actionButton.loading(row) : false;
                                const isProcessing = actionButton.isProcessing ? actionButton.isProcessing(row) : false;
                                const baseProps = {
                                  row, disabled: disabledResult, loading: isLoading,
                                  className: actionButton.className, title: actionTitle,
                                  idField: actionButton.idField ?? 'id', nameField: actionButton.nameField ?? 'name',
                                  typeField: actionButton.typeField ?? 'type', contentField: actionButton.contentField ?? 'content',
                                  operationName: actionButton.operationName, loadingStateName: actionButton.loadingStateName
                                };
                                switch (actionButton.type) {
                                  case 'edit':
                                    return <EditActionButton key={`action-${actionIndex}`} {...baseProps} onEdit={actionButton.onAction} hookData={hookData} />;
                                  case 'delete':
                                    return <DeleteActionButton key={`action-${actionIndex}`} {...baseProps} containerRef={{ current: actionButtonsRefs.current.get(index) || null }} hookData={hookData} />;
                                  case 'view':
                                    return <ViewActionButton key={`action-${actionIndex}`} {...baseProps} onView={actionButton.onAction || (() => {})} isViewing={isProcessing} hookData={hookData} />;
                                  case 'copy':
                                    return <CopyActionButton key={`action-${actionIndex}`} {...baseProps} onCopy={actionButton.onAction} isCopying={isProcessing} contentField={actionButton.contentField} />;
                                  default:
                                    return null;
                                }
                              })}
                              {customActions.map((customAction) => (
                                <CustomActionButton key={`custom-${customAction.id}`} row={row} id={customAction.id} icon={customAction.icon}
                                  onClick={customAction.onClick} visible={customAction.visible} disabled={customAction.disabled}
                                  loading={customAction.loading} title={customAction.title} className={customAction.className}
                                  hookData={hookData} idField={customAction.idField ?? 'id'} />
                              ))}
                            </div>
                          </td>
                        )}
                        {detectedColumns.map(column => {
                          const cellValue = row[column.key];
                          const customClassName = column.cellClassName ? column.cellClassName(cellValue, row) : '';
                          const combinedClassName = `${styles.td} ${customClassName}`.trim();
                          const isNumeric = column.type === 'number' || column.type === 'float' || column.type === 'integer';
                          return (
                            <td key={column.key} className={combinedClassName}
                              style={{ width: columnWidths[column.key] || column.width || 150, minWidth: columnWidths[column.key] || column.width || 150, maxWidth: columnWidths[column.key] || column.width || 150, ...(isNumeric ? { textAlign: 'right' } : {}) }}>
                              {formatCellValue(cellValue, column, row)}
                            </td>
                          );
                        })}
                      </tr>
                    );
                  })
                )}
                {!loading && displayData.length === 0 && (
                  <tr>
                    <td 
                      colSpan={(selectable ? 1 : 0) + (hasActionColumn ? 1 : 0) + detectedColumns.length}
                      style={{ textAlign: 'center', padding: '40px 16px', color: 'var(--text-muted, #64748b)' }}
                    >
                      {emptyMessage || t('Keine Daten verfügbar')}
                    </td>
                  </tr>
                )}
              </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

export default FormGeneratorTable;

