import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react';
import { useReactFlow } from 'reactflow';
import { useStore, Node, Edge } from '../store/store';
import { useSimpleReplaySync, ReplayStep } from '../hooks/useSimpleReplaySync';
import { useKeyboardShortcuts } from '../hooks/useKeyboardShortcuts';
import { useClipboard } from '../hooks/useClipboard';
import { useLocking } from '../hooks/useLocking';
import { useReplayCoordination } from '../hooks/useReplayCoordination';
import { useViewportEffects } from '../hooks/useViewportEffects';
import { useDragAndDrop } from '../hooks/useDragAndDrop';
import { useFlowEventHandlers } from '../hooks/useFlowEventHandlers';
import { useReplayIndicators } from '../hooks/useReplayIndicators';
import { useModalState } from '../hooks/useModalState';
import { useSearch } from '../hooks/useSearch';
import { useModelDataLoader } from '../hooks/useModelDataLoader';
import { useConfiguration } from '../hooks/useConfiguration';
import { useCloseHandler } from '../hooks/useCloseHandler';
import { useMessagesContainer } from '../hooks/useMessagesContainer';
import { useQuickAdd } from '../hooks/useQuickAdd';
import { useEdgeStyling } from '../hooks/useEdgeStyling';
import { useNodeEdgeActions } from '../hooks/useNodeEdgeActions';
import { useSelectionSync } from '../hooks/useSelectionSync';
import { useStatusAnnouncer } from '../hooks/useStatusAnnouncer';
import { useTestRunner } from '../hooks/useTestRunner';
import { useExport } from '../hooks/useExport';
import type { ExportFormat } from '../hooks/useExport';
import { useViewMode } from '../hooks/useViewMode';
import { exportModelData } from '../utils/modelUtils';
import { t } from '../utils/translation';
import type { ReplayEntry } from '../hooks/useReplayLoader';
import type { Settings, DrupalAjax } from '../types/settings';
import { hasPermission } from '../utils/permissions';

// Component imports
import FlowCanvas from './FlowCanvas';
import ComponentPanel from './ComponentPanel';
import PropertyPanel from './PropertyPanel';
import ReplayPanel from './ReplayPanel';
import Modals from './Modals';
import Toolbar from './Toolbar';
import PanelErrorBoundary from './PanelErrorBoundary';
import { FEATURE_FLAGS } from '../constants/featureFlags';


interface FlowProps {
  settings: Settings;
  drupal: DrupalAjax;
}

function FlowInner({ settings, drupal }: FlowProps) {
  // ReactFlow hooks
  const { fitView, setCenter, setViewport, getViewport } = useReactFlow();

  const modelerRef = useRef<HTMLDivElement>(null);

  // Messages container hook
  const {
    messagesContainerRef,
    messagesVisible,
    hasMessages,
    handleToggleMessages,
    handleClearMessages
  } = useMessagesContainer();

  // Context selection from store
  const selectedContextId = useStore(state => state.selectedContextId);
  const setSelectedContextId = useStore(state => state.setSelectedContextId);

  // Store state - single source of truth
  const nodes = useStore(state => state.nodes);
  const edges = useStore(state => state.edges);
  const setNodes = useStore(state => state.setNodes);
  const setEdges = useStore(state => state.setEdges);
  const selectedNode = useStore(state => state.selectedNode);
  const setSelectedNode = useStore(state => state.setSelectedNode);
  const selectedEdge = useStore(state => state.selectedEdge);
  const setSelectedEdge = useStore(state => state.setSelectedEdge);
  const selectedNodes = useStore(state => state.selectedNodes);
  const selectedEdges = useStore(state => state.selectedEdges);
  const setSelectedNodes = useStore(state => state.setSelectedNodes);
  const setSelectedEdges = useStore(state => state.setSelectedEdges);
  const modelData = useStore(state => state.modelData);
  const viewportTarget = useStore(state => state.viewportTarget);
  const setViewportTarget = useStore(state => state.setViewportTarget);
  const setComponentPanelCollapsed = useStore(state => state.setComponentPanelCollapsed);
  const setReplayPanelCollapsed = useStore(state => state.setReplayPanelCollapsed);

  // Use viewport effects to handle viewport changes
  useViewportEffects({
    viewportTarget,
    nodes,
    setCenter,
    fitView,
    onViewportChange: () => {
      setViewportTarget(null);
    }
  });

  // Read-only mode: no modifications are allowed when:
  //  1. explicitly set via drupalSettings.modeler_api.readOnly, OR
  //  2. the model is an existing template and the user lacks the
  //     "edit template" permission, OR
  //  3. running in standalone viewer mode (no Drupal backend).
  const isStandalone = !!settings.modeler?.standalone;

  // View mode: fullscreen (default Drupal) or restored (default standalone)
  const {
    viewMode,
    toggleViewMode,
    startDrag,
    startResize,
    isDragging,
    isResizing,
  } = useViewMode({ isStandalone, modelerRef });

  const isTemplateEditBlocked =
    !settings.modeler_api?.isNew &&
    !!settings.modeler_api?.metadata?.template &&
    !hasPermission(settings, 'edit template'); // eslint-disable-line i18n/no-untranslated-strings
  const isReadOnly = !!settings.modeler_api?.readOnly || isTemplateEditBlocked || isStandalone;

  // Local state for UI interactions
  const [isReplayMode, setIsReplayMode] = useState(false);
  const [currentReplayStep, setCurrentReplayStep] = useState(-1);
  const [showMinimap, setShowMinimap] = useState(false);
  const [showEdgeOrderNumbers, setShowEdgeOrderNumbers] = useState(false);
  const [showAllAnnotations, setShowAllAnnotations] = useState(false);
  const [isShiftPressed, setIsShiftPressed] = useState(false);
  const [isCtrlPressed, setIsCtrlPressed] = useState(false);
  const [isAltPressed, setIsAltPressed] = useState(false);
  const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
  const [isLockedByUser, setIsLockedByUser] = useState(false);
  // In read-only mode, the canvas is always locked regardless of user toggle
  const isLocked = isReadOnly || isLockedByUser;
  const [isEventPopupOpen, setIsEventPopupOpen] = useState(false);

  // Dynamically loaded replay entries from backend
  const [loadedReplayEntries, setLoadedReplayEntries] = useState<ReplayEntry[]>([]);
  const [selectedReplayEntryIndex, setSelectedReplayEntryIndex] = useState(-1);

  // Status announcer for screen readers (aria-live region)
  const { message: statusMessage, announce } = useStatusAnnouncer();

  // Search functionality
  const {
    isSearchVisible,
    searchTerm,
    highlightedSearchResult,
    onSearchHighlight,
    onSearchFocus,
    clearSearch,
    setIsSearchVisible
  } = useSearch();
  
  // Drag and drop functionality
  const {
    onDrop,
    onDragOver,
    onComponentDrag,
    onComponentDragEnd,
    onComponentDragStart,
    onConditionDragStart,
    onConditionDragEnd,
    isDraggingCondition,
    hoveredDropEdge,
    isDragActive
  } = useDragAndDrop({
    isLocked,
    setHasUnsavedChanges
  });

  // Modal state management
  const {
    showMetadataModal,
    showConfirmDialog,
    confirmDialogTitle,
    confirmDialogMessage,
    confirmDialogType,
    confirmDialogLoading,
    confirmDialogPrimaryLabel,
    confirmDialogSecondaryLabel,
    confirmDialogCancelLabel,
    confirmDialogPrimaryVariant,
    onMetadataSubmit,
    showConfirmationDialog,
    handleConfirmDialog,
    handleCancelDialog,
    handleCloseWithoutSave,
    openMetadataModal,
    closeMetadataModal
  } = useModalState({ setHasUnsavedChanges });

  // Close handler
  const {
    handleClose,
    handleMinimize,
    handleSaveComplete,
    saveButtonRef
  } = useCloseHandler({
    settings,
    hasUnsavedChanges,
    showConfirmationDialog
  });

  // Handle initial state based on whether this is a new model
  useEffect(() => {
    const isNew = settings?.modeler_api?.isNew;
    
    if (isNew) {
      setComponentPanelCollapsed(false);
      const timer = setTimeout(() => {
        openMetadataModal();
      }, 100);
      return () => clearTimeout(timer);
    } else {
      setComponentPanelCollapsed(true);
    }
  }, [settings?.modeler_api?.isNew, setComponentPanelCollapsed, openMetadataModal]);

  // Wrapper for closing metadata modal - opens event popup for new models
  const handleCloseMetadataModal = useCallback(() => {
    closeMetadataModal();
    if (settings?.modeler_api?.isNew) {
      setTimeout(() => {
        setIsEventPopupOpen(true);
      }, 150);
    }
  }, [closeMetadataModal, settings?.modeler_api?.isNew]);

  // Model data loading and management
  const { replayData: initialReplayData } = useModelDataLoader({ settings, setViewportTarget });

  // Active replay data: use selected entry's data if available, otherwise use initial data.
  // Memoized to keep a stable reference — many hooks depend on replayData identity.
  const replayData: ReplayStep[] = useMemo(() => {
    if (
      selectedReplayEntryIndex >= 0 &&
      selectedReplayEntryIndex < loadedReplayEntries.length
    ) {
      return (loadedReplayEntries[selectedReplayEntryIndex].history || []) as ReplayStep[];
    }
    return initialReplayData;
  }, [selectedReplayEntryIndex, loadedReplayEntries, initialReplayData]);
  
  // Configuration management
  const {
    onConfigurationChange,
    onEdgeConfigurationChange,
    onNodeUpdate,
    onEdgeUpdate,
    handleAutoLayout
  } = useConfiguration({ setHasUnsavedChanges });

  // Quick add functionality
  const { addSuccessorNode } = useQuickAdd({ setHasUnsavedChanges });

  // Node and edge action handlers (add condition, add event)
  const { handleAddCondition, handleAddEvent } = useNodeEdgeActions({ setHasUnsavedChanges });

  // Sync selected objects when nodes/edges change
  useSelectionSync();

  // Start flow filter from store
  const visibleStartNodeIds = useStore(state => state.visibleStartNodeIds);
  const setVisibleStartNodeIds = useStore(state => state.setVisibleStartNodeIds);

  // Track previous start node IDs so we can detect additions/removals.
  const prevStartNodeIdsRef = useRef<Set<string>>(new Set());

  // Keep the flow filter in sync when start nodes are added or removed.
  //  - Removed start nodes are pruned from the selection (revert to "All" if none remain).
  //  - Newly added start nodes are auto-included so they are immediately visible.
  //
  // The ref starts empty.  On the first run (initial model load) we only
  // populate it — we must NOT treat every loaded node as "newly added",
  // because that would immediately override a filter that was set during
  // model loading (e.g. via selectComponentId).
  useEffect(() => {
    const currentStartIds = new Set(
      nodes.filter(n => n.type === 'start').map(n => n.id),
    );
    const prevIds = prevStartNodeIdsRef.current;

    // First meaningful population — just record and bail.
    if (prevIds.size === 0 && currentStartIds.size > 0) {
      prevStartNodeIdsRef.current = currentStartIds;
      return;
    }

    prevStartNodeIdsRef.current = currentStartIds;

    if (visibleStartNodeIds === null) return;

    // Detect start nodes that were just added to the canvas.
    const added: string[] = [];
    currentStartIds.forEach(id => {
      if (!prevIds.has(id)) added.push(id);
    });

    // Remove IDs that no longer exist on the canvas.
    let next = visibleStartNodeIds.filter(id => currentStartIds.has(id));

    // Auto-include any newly added start nodes in the selection.
    for (const id of added) {
      if (!next.includes(id)) {
        next = [...next, id];
      }
    }

    // If every start node is now selected (or none remain), revert to "All".
    if (next.length === 0 || next.length === currentStartIds.size) {
      if (visibleStartNodeIds !== null) {
        setVisibleStartNodeIds(null);
      }
    } else if (
      next.length !== visibleStartNodeIds.length ||
      next.some((id, i) => id !== visibleStartNodeIds[i])
    ) {
      setVisibleStartNodeIds(next);
    }
  }, [nodes, visibleStartNodeIds, setVisibleStartNodeIds]);

  // Compute the set of visible node IDs based on the start flow filter.
  // Uses BFS from each visible start node to discover all reachable nodes.
  const visibleNodeIds = useMemo<Set<string> | null>(() => {
    if (visibleStartNodeIds === null) return null; // All visible

    const reachable = new Set<string>();
    const adjacency = new Map<string, string[]>();

    // Build adjacency from edges
    for (const edge of edges) {
      if (!adjacency.has(edge.source)) adjacency.set(edge.source, []);
      adjacency.get(edge.source)!.push(edge.target);
    }

    // BFS from each selected start node
    const queue: string[] = [...visibleStartNodeIds];
    for (const id of queue) reachable.add(id);

    while (queue.length > 0) {
      const current = queue.shift()!;
      const neighbors = adjacency.get(current);
      if (neighbors) {
        for (const neighbor of neighbors) {
          if (!reachable.has(neighbor)) {
            reachable.add(neighbor);
            queue.push(neighbor);
          }
        }
      }
    }

    return reachable;
  }, [visibleStartNodeIds, edges]);

  // Apply the flow filter: set `hidden` on nodes and edges not in the visible set.
  const filteredNodes = useMemo(() => {
    if (visibleNodeIds === null) {
      // Remove any stale hidden flags
      return nodes.map(n => n.hidden ? { ...n, hidden: false } : n);
    }
    return nodes.map(n => ({
      ...n,
      hidden: !visibleNodeIds.has(n.id),
    }));
  }, [nodes, visibleNodeIds]);

  const filteredEdges = useMemo(() => {
    if (visibleNodeIds === null) {
      return edges.map(e => e.hidden ? { ...e, hidden: false } : e);
    }
    return edges.map(e => ({
      ...e,
      hidden: !visibleNodeIds.has(e.source) || !visibleNodeIds.has(e.target),
    }));
  }, [edges, visibleNodeIds]);

  // Edge styling for condition drag-and-drop
  const styledEdges = useEdgeStyling({ edges: filteredEdges, isDraggingCondition, hoveredDropEdge });

  // Replay indicators
  const { replayIndicators } = useReplayIndicators({
    isReplayMode,
    currentReplayStep,
    replayData,
    edges,
    nodes,
  });

  // Initialize custom hooks
  const { isSyncing, handleCanvasNodeClick, handleCanvasEdgeClick, handleReplayStepSelect: baseHandleReplayStepSelect } = useSimpleReplaySync({
    replayData,
    nodes,
    edges,
    setNodes,
    setEdges,
    setSelectedNode,
    setSelectedEdge,
    currentStep: currentReplayStep,
    setCurrentStep: setCurrentReplayStep,
  });

  // Wrap handleReplayStepSelect to also activate replay mode
  const handleReplayStepSelect = useCallback((stepIndex: number) => {
    baseHandleReplayStepSelect(stepIndex);
    if (stepIndex >= 0 && !isReplayMode) {
      setIsReplayMode(true);
    }
  }, [baseHandleReplayStepSelect, isReplayMode, setIsReplayMode]);

  const { handleCopy, handlePaste, canCopy, canPaste } = useClipboard({
    selectedNode,
    selectedEdge,
    selectedNodeIds: selectedNodes,
    selectedEdgeIds: selectedEdges,
    nodes,
    edges,
    setNodes,
    setEdges,
    setSelectedNode,
    setSelectedEdge,
    setSelectedNodes,
    setSelectedEdges,
    setHasUnsavedChanges,
    announce,
  });

  const { isNodeLocked, isEdgeLocked, canDeleteSelected } = useLocking({
    nodes,
    edges,
    selectedNode,
    selectedEdge,
  });

  const { autoSyncToReplay, toggleReplayMode, hasReplayData } = useReplayCoordination({
    replayData,
    nodes,
    edges,
    isReplayMode,
    currentReplayStep,
    setIsReplayMode,
    setCurrentReplayStep,
    selectedNode,
    selectedEdge,
    isSyncing,
    handleReplayStepSelect,
  });

  // Derive the selected start node ID (or auto-detect if there's only one start node)
  const selectedStartNodeId = useMemo(() => {
    if (selectedNode?.type === 'start') return selectedNode.id;
    const startNodes = nodes.filter(n => n.type === 'start');
    if (startNodes.length === 1) return startNodes[0].id;
    return null;
  }, [selectedNode, nodes]);

  const isNewModel = !!settings.modeler_api?.isNew;

  // Granular permission checks (string keys are permission identifiers, not UI text)
  /* eslint-disable i18n/no-untranslated-strings */
  const canEditMetadata = hasPermission(settings, 'edit metadata');
  const canSwitchContext = hasPermission(settings, 'switch context');
  const canEditTemplate = hasPermission(settings, 'edit template');
  const canCreateTemplate = hasPermission(settings, 'create template');
  const canTest = hasPermission(settings, 'test');
  const canReplay = hasPermission(settings, 'replay');
  /* eslint-enable i18n/no-untranslated-strings */

  // Note: "edit template" restriction is now enforced via isReadOnly
  // (computed earlier) — when the model is an existing template and the
  // user lacks the permission, the entire modeler enters read-only mode.

  // Testing implies replay — hide the test button when replay is denied.
  // In standalone mode test/replay URLs are not available but data may
  // have been embedded in the JSON export.
  const hasTestUrl = !isStandalone && !isNewModel && !!settings.modeler_api?.test_url && canTest && canReplay;
  const hasReplayUrl = !isStandalone && !isNewModel && !!settings.modeler_api?.replay_url && canReplay;
  const hasAnyReplayCapability = hasTestUrl || hasReplayUrl || initialReplayData.length > 0;

  // Event handlers from extracted hook
  const {
    onNodesChange,
    onEdgesChange,
    onSelectionChange,
    onNodeClick,
    onEdgeClick,
    onDeleteNode,
    handleDeleteSelected,
    onConnect,
    onPaneClick,
    onNodeDragStart,
    onNodeDragStop,
    onViewportChange
  } = useFlowEventHandlers({
    handleCanvasNodeClick,
    handleCanvasEdgeClick,
    isNodeLocked,
    isEdgeLocked,
    setHasUnsavedChanges,
    isSyncing,
    hasReplayData,
    isReplayMode,
    currentReplayStep,
    autoSyncToReplay,
    announce
  });

  // Handle delete with confirmation for multi-selection panel
  const handleDeleteSelectedWithConfirm = useCallback(() => {
    const totalItems = selectedNodes.length + selectedEdges.length;
    if (totalItems === 0) return;

    showConfirmationDialog(
      t('Delete Selected Items'),
      t('Are you sure you want to delete @count selected items? This action cannot be undone.', { '@count': String(totalItems) }),
      'danger',
      () => { handleDeleteSelected(); },
      undefined,
      {
        primaryLabel: t('Delete'),
        secondaryLabel: false,
        cancelLabel: t('Cancel'),
        primaryVariant: 'danger',
      }
    );
  }, [selectedNodes.length, selectedEdges.length, showConfirmationDialog, handleDeleteSelected]);

  // Keyboard shortcuts (copy/paste/delete disabled in read-only mode)
  useKeyboardShortcuts({
    callbacks: {
      onDelete: handleDeleteSelected,
      onCopy: handleCopy,
      onPaste: handlePaste,
      onToggleSearch: () => setIsSearchVisible(prev => !prev),
      onEscape: () => {
        setIsSearchVisible(false);
        clearSearch();
      },
    },
    modifiers: {
      isShiftPressed,
      setIsShiftPressed,
      isCtrlPressed,
      setIsCtrlPressed,
      isAltPressed,
      setIsAltPressed,
    },
    capabilities: {
      canDelete: !isReadOnly && canDeleteSelected(),
      canCopy: !isReadOnly && canCopy,
      canPaste: !isReadOnly && canPaste,
      canSearch: true,
      canEscape: true,
    },
    isModelerFocused: true,
    getViewport,
    setViewport,
    enabled: true,
  });

  // Handle dynamically loaded replay entries from PropertyPanel
  const handleReplayEntriesLoaded = useCallback((entries: ReplayEntry[]) => {
    setLoadedReplayEntries(entries);
    // If there are entries, auto-select the first one and open replay
    if (entries.length > 0) {
      setSelectedReplayEntryIndex(0);
      const firstEntryData = (entries[0].history || []) as ReplayStep[];
      if (firstEntryData.length > 0) {
        setIsReplayMode(true);
        setCurrentReplayStep(-1);
      }
      // Auto-expand the replay panel if it's collapsed
      setReplayPanelCollapsed(false);
      announce(t('@count replay entries loaded', { '@count': String(entries.length) }));
    } else {
      setSelectedReplayEntryIndex(-1);
    }
  }, [setIsReplayMode, setCurrentReplayStep, announce, setReplayPanelCollapsed]);

  // Handle selecting a replay entry from the dropdown
  const handleSelectReplayEntry = useCallback((index: number) => {
    setSelectedReplayEntryIndex(index);
    // Reset replay step when switching entries
    setCurrentReplayStep(-1);
    if (index >= 0 && index < loadedReplayEntries.length) {
      const entryData = (loadedReplayEntries[index].history || []) as ReplayStep[];
      if (entryData.length > 0) {
        setIsReplayMode(true);
      }
    }
  }, [loadedReplayEntries, setCurrentReplayStep, setIsReplayMode]);

  // Handle replay data received from the test runner
  const handleTestReplayDataReceived = useCallback((data: any[]) => {
    // Wrap the raw replay steps into a ReplayEntry-like structure
    const entry: ReplayEntry = {
      model_id: settings.modeler?.modelId || '',
      component_id: selectedStartNodeId || '',
      history: data,
      timestamp: new Date().toISOString(),
      user: 'test',
      ip: '',
      url: '',
    };
    handleReplayEntriesLoaded([entry]);
    announce(t('Test completed — replay data received'));
  }, [settings.modeler?.modelId, selectedStartNodeId, handleReplayEntriesLoaded, announce]);

  // Test runner hook
  const {
    isTestRunning,
    isTestInitiating,
    testError,
    startTest,
    cancelTest,
    notifySaveComplete,
  } = useTestRunner({
    settings,
    hasUnsavedChanges,
    showConfirmationDialog,
    saveButtonRef,
    onReplayDataReceived: handleTestReplayDataReceived,
  });

  // Components from store (needed for export to derive required modules)
  const components = useStore(state => state.components);

  // Export functionality
  const [showExportDialog, setShowExportDialog] = useState(false);
  const [isExporting, setIsExporting] = useState(false);
  const pendingExportAfterSaveRef = useRef(false);

  const {
    canExport,
    availableFormats,
    hasReplayData: exportHasReplayData,
    executeExport,
    getRequiredModules,
  } = useExport({
    settings,
    nodes,
    edges,
    components,
    modelData,
    replayData,
    announce,
  });

  // Handle the export button click: check unsaved changes, then open dialog
  // In standalone mode there is nothing to save — go straight to dialog.
  const handleExportClick = useCallback(() => {
    if (hasUnsavedChanges && !isStandalone) {
      showConfirmationDialog(
        t('Unsaved Changes'),
        t('You have unsaved changes. Please save your model before exporting.'),
        'warning',
        () => {
          // Mark that we want to open the export dialog after save completes
          pendingExportAfterSaveRef.current = true;
          saveButtonRef.current?.click();
        },
        undefined,
        {
          primaryLabel: t('Save Now'),
          secondaryLabel: false,
          cancelLabel: t('Cancel'),
          primaryVariant: 'primary',
        },
      );
      return;
    }
    setShowExportDialog(true);
  }, [hasUnsavedChanges, isStandalone, showConfirmationDialog, saveButtonRef]);

  // Handle format selection from the export dialog
  const handleExportFormat = useCallback(async (format: ExportFormat, includeReplayData?: boolean) => {
    setIsExporting(true);
    try {
      await executeExport(format, includeReplayData);
      setShowExportDialog(false);
    } catch (_error) {
      // Error is already announced via the hook
    } finally {
      setIsExporting(false);
    }
  }, [executeExport]);

  const handleCloseExportDialog = useCallback(() => {
    setShowExportDialog(false);
  }, []);

  // Auto-collapse replay panel when there's no data and no test running.
  // Auto-expand when the Test button becomes available so users can access it.
  const showTestButton = hasTestUrl && !!selectedStartNodeId;
  useEffect(() => {
    const hasData = replayData && replayData.length > 0;
    if (!hasData && !isTestRunning && !isTestInitiating && !showTestButton) {
      setReplayPanelCollapsed(true);
    } else if (showTestButton || hasData || isTestRunning || isTestInitiating) {
      setReplayPanelCollapsed(false);
    }
  }, [replayData, isTestRunning, isTestInitiating, showTestButton, setReplayPanelCollapsed]);

  // Placeholder handlers for other features
  const onConnectStart = useCallback(() => {}, []);
  const onConnectEnd = useCallback(() => {}, []);
  const onDragEnter = useCallback(() => {}, []);
  const onDragLeave = useCallback(() => {}, []);
  const onInit = useCallback(() => {}, []);

  return (
    <div
      className={[
        'workflow-modeler',
        isStandalone ? 'standalone' : '',
        viewMode,
        isDragging ? 'is-dragging' : '',
        isResizing ? 'is-resizing' : '',
      ].filter(Boolean).join(' ')}
      ref={modelerRef}
    >
      <PanelErrorBoundary panelName={t('Toolbar')} className="toolbar-error">
        <Toolbar
          modelName={modelData?.metadata?.label || t('Untitled Workflow')}
          hasUnsavedChanges={hasUnsavedChanges}
          onSave={() => exportModelData(nodes, edges, { id: modelData?.id, version: modelData?.version, ...modelData?.metadata })}
          onSaveComplete={() => {
            handleSaveComplete(setHasUnsavedChanges);
            notifySaveComplete();
            if (pendingExportAfterSaveRef.current) {
              pendingExportAfterSaveRef.current = false;
              setShowExportDialog(true);
            }
          }}
          saveButtonRef={saveButtonRef}
          onOpenMetadata={openMetadataModal}
          onAutoLayout={handleAutoLayout}
          onToggleLock={() => setIsLockedByUser(!isLockedByUser)}
          onToggleMinimap={() => setShowMinimap(!showMinimap)}
          onCopy={handleCopy}
          onPaste={handlePaste}
          onToggleSearch={() => setIsSearchVisible(!isSearchVisible)}
          onToggleAllAnnotations={() => setShowAllAnnotations(!showAllAnnotations)}
          onToggleMessages={handleToggleMessages}
          onClearMessages={handleClearMessages}
          onToggleEdgeOrders={() => setShowEdgeOrderNumbers(!showEdgeOrderNumbers)}
          isLocked={isLocked}
          isReadOnly={isReadOnly}
          isSearchVisible={isSearchVisible}
          isMinimapVisible={showMinimap}
          hasSelection={!!selectedNode || !!selectedEdge}
          canPaste={canPaste}
          nodes={nodes}
          edges={edges}
          allAnnotationsVisible={showAllAnnotations}
          edgeOrdersVisible={showEdgeOrderNumbers}
          hasMessages={hasMessages}
          messagesVisible={messagesVisible}
          onSearchHighlight={onSearchHighlight}
          onSearchFocus={onSearchFocus}
          settings={settings}
          drupal={drupal}
          onClose={handleClose}
          onMinimize={handleMinimize}
          announce={announce}
          contexts={canSwitchContext && !isReadOnly ? settings?.modeler_api?.contexts : []}
          selectedContextId={selectedContextId}
          onContextChange={setSelectedContextId}
          onAddEvent={handleAddEvent}
          isEventPopupOpen={isEventPopupOpen}
          onEventPopupOpenChange={setIsEventPopupOpen}
          onExport={handleExportClick}
          canExport={canExport}
          viewMode={viewMode}
          onToggleViewMode={toggleViewMode}
          onStartDrag={startDrag}
        />
      </PanelErrorBoundary>

      {/* Messages container - positioned at top, fades out */}
      <div
        ref={messagesContainerRef}
        role="log"
        aria-label={t('Workflow messages')}
        aria-live="polite"
        aria-relevant="additions removals"
        className={`workflow-messages-container ${messagesVisible ? 'visible' : 'hidden'}`}
      />

      <div className="workflow-modeler-content">
        {/* Component Panel - Left side (hidden in read-only mode) */}
        {FEATURE_FLAGS.SHOW_COMPONENT_PANEL && !isReadOnly && (
          <PanelErrorBoundary panelName={t('Components')} className="component-panel-error">
            <ComponentPanel
              onComponentDrag={onComponentDrag}
              onComponentDragEnd={onComponentDragEnd}
              onConditionDragStart={onConditionDragStart}
              onConditionDragEnd={onConditionDragEnd}
              onComponentDragStart={onComponentDragStart}
            />
          </PanelErrorBoundary>
        )}

        {/* Main Canvas */}
        <div className={`workflow-modeler-canvas ${isDraggingCondition ? 'condition-drag-active' : ''}`}>
          <PanelErrorBoundary panelName={t('Canvas')} className="canvas-error">
            <FlowCanvas
              nodes={filteredNodes}
              edges={styledEdges}
              eventHandlers={{
                onNodesChange,
                onEdgesChange,
                onConnect,
                onSelectionChange,
                onConnectStart,
                onConnectEnd,
                onDrop,
                onDragOver,
                onDragEnter,
                onDragLeave,
                onNodeClick,
                onEdgeClick,
                onPaneClick,
                onNodeDragStart,
                onNodeDragStop,
                onInit,
              }}
              elementCallbacks={{
                onEdgeUpdate,
                onNodeUpdate,
                onDeleteNode,
              }}
              viewport={getViewport()}
              onViewportChange={onViewportChange}
              modifierKeys={{
                isShiftPressed,
                isCtrlPressed,
                isAltPressed,
              }}
              uiState={{
                isDragActive,
                isLocked,
                showMinimap,
                showEdgeOrderNumbers,
                showAllAnnotations,
              }}
              search={{
                searchTerm,
                highlightedSearchResult,
              }}
              replay={{
                replayData,
                currentReplayStep,
                isReplayMode,
                replayIndicators,
              }}
              setEdges={setEdges}
              setHasUnsavedChanges={setHasUnsavedChanges}
              quickAdd={{
                onQuickAdd: addSuccessorNode,
                onAddCondition: handleAddCondition,
              }}
            />
          </PanelErrorBoundary>
        </div>

        {/* Replay Panel - Hidden when no test_url/replay_url and no initial data */}
        {hasAnyReplayCapability && (
          <PanelErrorBoundary panelName={t('Replay')} className="replay-panel-error">
            <ReplayPanel
              replayData={replayData}
              currentStep={currentReplayStep}
              onSelectStep={handleReplayStepSelect}
              onToggleReplay={toggleReplayMode}
              isReplayMode={isReplayMode}
              edges={edges}
              nodes={nodes}
              isVisible={true}
              stepData={currentReplayStep >= 0 && currentReplayStep < replayData.length ? replayData[currentReplayStep].data || null : null}
              stepInfo={currentReplayStep >= 0 && currentReplayStep < replayData.length ? {
                type: replayData[currentReplayStep].type,
                id: replayData[currentReplayStep].id,
                successorId: replayData[currentReplayStep].successorId,
                conditionId: replayData[currentReplayStep].conditionId,
                object: replayData[currentReplayStep].object,
                exception: replayData[currentReplayStep].exception
              } : null}
              replayEntries={loadedReplayEntries}
              selectedEntryIndex={selectedReplayEntryIndex}
              onSelectReplayEntry={handleSelectReplayEntry}
              selectedStartNodeId={selectedStartNodeId}
              hasReplayUrl={hasReplayUrl}
              hasTestUrl={hasTestUrl}
              isTestRunning={isTestRunning}
              isTestInitiating={isTestInitiating}
              testError={testError}
              onStartTest={startTest}
              onCancelTest={cancelTest}
              globalTokens={settings.modeler_api?.global_tokens}
              templateTokens={settings.modeler_api?.template_tokens}
              isTemplate={!!settings.modeler_api?.metadata?.template}
            />
          </PanelErrorBoundary>
        )}

        {/* Property Panel - Far right */}
        <PanelErrorBoundary panelName={t('Properties')} className="property-panel-error">
          <PropertyPanel
            node={selectedNode}
            edge={selectedEdge}
            selectedNodes={selectedNodes.map(id => nodes.find(n => n.id === id)).filter((n): n is Node => n !== undefined)}
            selectedEdges={selectedEdges.map(id => edges.find(e => e.id === id)).filter((e): e is Edge => e !== undefined)}
            onConfigurationChange={onConfigurationChange}
            onEdgeConfigurationChange={onEdgeConfigurationChange}
            onNodeUpdate={onNodeUpdate}
            onEdgeUpdate={onEdgeUpdate}
            onDeleteSelected={handleDeleteSelectedWithConfirm}
            settings={settings}
            isLocked={isLocked}
            onReplayEntriesLoaded={handleReplayEntriesLoaded}
          />
        </PanelErrorBoundary>
      </div>

      <PanelErrorBoundary panelName={t('Modals')} className="modals-error">
        <Modals
          showMetadataModal={showMetadataModal}
          onCloseMetadataModal={handleCloseMetadataModal}
          onMetadataSubmit={onMetadataSubmit}
          modelMetadata={modelData?.metadata || { label: '', description: '', tags: [], changelog: '' }}
          modelId={modelData?.id}
          isNewModel={settings?.modeler_api?.isNew || false}
          canEditMetadata={canEditMetadata && !isReadOnly}
          canCreateTemplate={canCreateTemplate && canEditTemplate && !isReadOnly}
          showConfirmDialog={showConfirmDialog}
          confirmDialogTitle={confirmDialogTitle}
          confirmDialogMessage={confirmDialogMessage}
          confirmDialogType={confirmDialogType}
          onConfirmDialog={handleConfirmDialog}
          onCancelDialog={handleCancelDialog}
          onCloseWithoutSave={handleCloseWithoutSave}
          confirmDialogLoading={confirmDialogLoading}
          confirmDialogPrimaryLabel={confirmDialogPrimaryLabel}
          confirmDialogSecondaryLabel={confirmDialogSecondaryLabel}
          confirmDialogCancelLabel={confirmDialogCancelLabel}
          confirmDialogPrimaryVariant={confirmDialogPrimaryVariant}
          showExportDialog={showExportDialog}
          onCloseExportDialog={handleCloseExportDialog}
          exportAvailableFormats={availableFormats}
          exportHasReplayData={exportHasReplayData}
          exportRequiredModules={getRequiredModules()}
          onExport={handleExportFormat}
          isExporting={isExporting}
        />
      </PanelErrorBoundary>

      {/* Resize handle — only in Drupal restored (windowed) mode */}
      {viewMode === 'restored' && !isStandalone && (
        <div
          className="modeler-resize-handle"
          onMouseDown={startResize}
          aria-hidden="true"
        />
      )}

      {/* Hidden aria-live region for screen reader announcements */}
      <div
        role="status"
        aria-live="polite"
        aria-atomic="true"
        className="sr-only"
      >
        {statusMessage}
      </div>
    </div>
  );
}

// Main Flow component wrapper
function Flow(props: FlowProps) {
  return <FlowInner {...props} />;
}

export default Flow;
