/**
 * Automation2FlowEditor
 *
 * n8n-style flow builder with backend-driven node list.
 * Workflow configuration (gear): primary start kind + invocations; canvas start node stays in sync.
 */

import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import { FaSpinner } from 'react-icons/fa';
import { useApiRequest } from '../../../hooks/useApi';
import {
  fetchNodeTypes,
  executeGraph,
  fetchWorkflows,
  fetchWorkflow,
  createWorkflow,
  updateWorkflow,
  fetchVersions,
  createDraftVersion,
  publishVersion,
  unpublishVersion,
  archiveVersion,
  createTemplateFromWorkflow,
  copyTemplate,
  type NodeType,
  type NodeTypeCategory,
  type Automation2Graph,
  type Automation2Workflow,
  type ExecuteGraphResponse,
  type WorkflowEntryPoint,
  type AutoVersion,
  type AutoTemplateScope,
} from '../../../api/workflowApi';
import { FlowCanvas, type CanvasNode, type CanvasConnection } from './FlowCanvas';
import { NodeConfigPanel } from './NodeConfigPanel';
import { NodeSidebar } from './NodeSidebar';
import { CanvasHeader } from './CanvasHeader';
import { WorkflowConfigurationModal } from './WorkflowConfigurationModal';
import { TemplatePicker } from './TemplatePicker';
import { getCategoryIcon } from '../nodes/shared/utils';
import { fromApiGraph, toApiGraph } from '../nodes/shared/graphUtils';
import {
  syncCanvasStartNode,
  buildInvocationsForPrimaryKind,
} from '../nodes/runtime/workflowStartSync';
import { buildNodeOutputsPreview, setPortTypeCatalog as setRegistryCatalog } from '../nodes/shared/outputPreviewRegistry';
import { Automation2DataFlowProvider } from '../context/Automation2DataFlowContext';
import { usePrompt } from '../../../hooks/usePrompt';
import { EditorChatPanel } from './EditorChatPanel';
import type { PendingFile, EditorDataSource, EditorFeatureDataSource } from './EditorChatPanel';
import { RunTracingPanel } from './RunTracingPanel';
import { UnifiedDataBar } from '../../../components/UnifiedDataBar';
import type { UdbContext, UdbTab } from '../../../components/UnifiedDataBar';
import styles from './Automation2FlowEditor.module.css';

import { useLanguage } from '../../../providers/language/LanguageContext';

const LOG = '[Automation2]';

const _buildDefaultInvocations = (runLabel: string): WorkflowEntryPoint[] =>
  buildInvocationsForPrimaryKind('manual', [], runLabel);

interface Automation2FlowEditorProps {
  instanceId: string;
  mandateId?: string;
  language?: string;
  /** When set, load this workflow on mount (e.g. from workflows list edit) */
  initialWorkflowId?: string | null;
  pendingFiles?: PendingFile[];
  onRemovePendingFile?: (fileId: string) => void;
  dataSources?: EditorDataSource[];
  featureDataSources?: EditorFeatureDataSource[];
  onFileSelect?: (fileId: string, fileName?: string) => void;
  onSourcesChanged?: () => void;
}

export const Automation2FlowEditor: React.FC<Automation2FlowEditorProps> = ({ instanceId,
  mandateId,
  language = 'de',
  initialWorkflowId,
  pendingFiles,
  onRemovePendingFile,
  dataSources,
  featureDataSources,
  onFileSelect,
  onSourcesChanged,
}) => {
  const { t } = useLanguage();
  const { request } = useApiRequest();
  const { prompt: promptInput, PromptDialog } = usePrompt();
  const [nodeTypes, setNodeTypes] = useState<NodeType[]>([]);
  const [categories, setCategories] = useState<NodeTypeCategory[]>([]);
  const [portTypeCatalog, setPortTypeCatalog] = useState<Record<string, unknown>>({});
  const [systemVariables, setSystemVariables] = useState<Record<string, unknown>>({});
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [filter, setFilter] = useState('');
  const [expandedCategories, setExpandedCategories] = useState<Set<string>>(
    new Set(['trigger', 'input', 'flow', 'data', 'ai', 'email', 'sharepoint', 'clickup', 'trustee'])
  );
  const [canvasNodes, setCanvasNodes] = useState<CanvasNode[]>([]);
  const [canvasConnections, setCanvasConnections] = useState<CanvasConnection[]>([]);
  const [executing, setExecuting] = useState(false);
  const [executeResult, setExecuteResult] = useState<ExecuteGraphResponse | null>(null);
  const [workflows, setWorkflows] = useState<Automation2Workflow[]>([]);
  const [currentWorkflowId, setCurrentWorkflowId] = useState<string | null>(null);
  const [selectedNode, setSelectedNode] = useState<CanvasNode | null>(null);
  const [saving, setSaving] = useState(false);
  const [invocations, setInvocations] = useState<WorkflowEntryPoint[]>(() =>
    _buildDefaultInvocations(t('Jetzt ausführen'))
  );
  const [workflowSettingsOpen, setWorkflowSettingsOpen] = useState(false);
  const [leftPanelOpen, setLeftPanelOpen] = useState(true);
  const [tracingRunId, setTracingRunId] = useState<string | null>(null);
  const [tracingNodeStatuses, setTracingNodeStatuses] = useState<Record<string, string>>({});
  const [rightTab, setRightTab] = useState<'nodes' | 'tracing'>('nodes');
  const [udbTab, setUdbTab] = useState<UdbTab>('chats');

  const udbContext: UdbContext = useMemo(() => ({
    instanceId,
    mandateId: mandateId || '',
    featureInstanceId: instanceId,
  }), [instanceId, mandateId]);
  const [versions, setVersions] = useState<AutoVersion[]>([]);
  const [currentVersionId, setCurrentVersionId] = useState<string | null>(null);
  const [versionLoading, setVersionLoading] = useState(false);

  const [leftPanelWidth, setLeftPanelWidth] = useState(() => {
    try { const v = parseInt(localStorage.getItem('flowEditor.leftPanelWidth') ?? ''); return v >= 240 && v <= 600 ? v : 340; } catch { return 340; }
  });
  const [sidebarWidth, setSidebarWidth] = useState(() => {
    try { const v = parseInt(localStorage.getItem('flowEditor.sidebarWidth') ?? ''); return v >= 200 && v <= 500 ? v : 280; } catch { return 280; }
  });
  const resizingRef = useRef<{ target: 'left' | 'right'; startX: number; startW: number } | null>(null);

  useEffect(() => {
    const _onMouseMove = (e: MouseEvent) => {
      if (!resizingRef.current) return;
      const { target, startX, startW } = resizingRef.current;
      const delta = e.clientX - startX;
      if (target === 'left') {
        setLeftPanelWidth(Math.max(240, Math.min(600, startW + delta)));
      } else {
        setSidebarWidth(Math.max(200, Math.min(500, startW - delta)));
      }
    };
    const _onMouseUp = () => {
      if (!resizingRef.current) return;
      const { target } = resizingRef.current;
      resizingRef.current = null;
      document.body.style.cursor = '';
      document.body.style.userSelect = '';
      if (target === 'left') {
        setLeftPanelWidth((w) => { try { localStorage.setItem('flowEditor.leftPanelWidth', String(w)); } catch {} return w; });
      } else {
        setSidebarWidth((w) => { try { localStorage.setItem('flowEditor.sidebarWidth', String(w)); } catch {} return w; });
      }
    };
    document.addEventListener('mousemove', _onMouseMove);
    document.addEventListener('mouseup', _onMouseUp);
    return () => { document.removeEventListener('mousemove', _onMouseMove); document.removeEventListener('mouseup', _onMouseUp); };
  }, []);

  const _startResize = useCallback((target: 'left' | 'right', e: React.MouseEvent) => {
    e.preventDefault();
    resizingRef.current = { target, startX: e.clientX, startW: target === 'left' ? leftPanelWidth : sidebarWidth };
    document.body.style.cursor = 'col-resize';
    document.body.style.userSelect = 'none';
  }, [leftPanelWidth, sidebarWidth]);

  const sidebarExcludedCategories = useMemo(() => new Set(['trigger']), []);

  const nodeOutputsPreview = useMemo(
    () =>
      buildNodeOutputsPreview(canvasNodes, nodeTypes, executeResult?.nodeOutputs as Record<string, unknown> | undefined),
    [canvasNodes, nodeTypes, executeResult?.nodeOutputs]
  );

  const applyGraphWithSync = useCallback(
    (graph: Automation2Graph | null | undefined, wfInvocations: WorkflowEntryPoint[] | undefined) => {
      const inv = wfInvocations?.length ? wfInvocations : _buildDefaultInvocations(t('Jetzt ausführen'));
      setInvocations(inv);
      if (!graph?.nodes?.length) {
        const synced = syncCanvasStartNode([], [], inv, nodeTypes, language);
        setCanvasNodes(synced.nodes);
        setCanvasConnections(synced.connections);
        return;
      }
      const { nodes, connections } = fromApiGraph(graph, nodeTypes);
      const synced = syncCanvasStartNode(nodes, connections, inv, nodeTypes, language);
      setCanvasNodes(synced.nodes);
      setCanvasConnections(synced.connections);
    },
    [nodeTypes, language, t]
  );

  const handleFromApiGraph = useCallback(
    (graph: Automation2Graph, wfInvocations?: WorkflowEntryPoint[]) => {
      applyGraphWithSync(graph, wfInvocations);
    },
    [applyGraphWithSync]
  );

  const handleExecute = useCallback(async () => {
    const graph = toApiGraph(canvasNodes, canvasConnections);
    if (graph.nodes.length === 0) {
      setExecuteResult({ success: false, error: t('Keine Nodes im Workflow.') });
      return;
    }
    setExecuting(true);
    setExecuteResult(null);
    try {
      const ep = currentWorkflowId ? invocations[0]?.id : undefined;
      const result = await executeGraph(request, instanceId, graph, currentWorkflowId ?? undefined, {
        ...(ep ? { entryPointId: ep } : {}),
      });
      setExecuteResult(result);
      if (result.runId) {
        setTracingRunId(result.runId);
        setRightTab('tracing');
      }
    } catch (err: unknown) {
      setExecuteResult({ success: false, error: err instanceof Error ? err.message : String(err) });
    } finally {
      setExecuting(false);
    }
  }, [request, instanceId, canvasNodes, canvasConnections, currentWorkflowId, invocations, t]);

  const handleSave = useCallback(async () => {
    const graph = toApiGraph(canvasNodes, canvasConnections);
    if (graph.nodes.length === 0) {
      setExecuteResult({ success: false, error: t('Keine Nodes zum Speichern.') });
      return;
    }
    setSaving(true);
    try {
      if (currentWorkflowId) {
        await updateWorkflow(request, instanceId, currentWorkflowId, { graph, invocations });
        setExecuteResult({ success: true } as ExecuteGraphResponse);
      } else {
        const label = await promptInput(t('Workflow-Name:'), {
          title: t('Workflow speichern'),
          defaultValue: t('Neuer Workflow'),
          placeholder: t('Name des Workflows'),
        });
        if (!label) {
          setSaving(false);
          return;
        }
        const created = await createWorkflow(request, instanceId, {
          label: label.trim() || t('Neuer Workflow'),
          graph,
          invocations,
        });
        setCurrentWorkflowId(created.id);
        if (created.invocations?.length) setInvocations(created.invocations);
        setWorkflows((prev) => [...prev, created]);
        setExecuteResult({ success: true } as ExecuteGraphResponse);
      }
    } catch (err: unknown) {
      setExecuteResult({ success: false, error: err instanceof Error ? err.message : String(err) });
    } finally {
      setSaving(false);
    }
  }, [request, instanceId, canvasNodes, canvasConnections, currentWorkflowId, promptInput, invocations, t]);

  const handleLoad = useCallback(
    async (workflowId: string) => {
      try {
        const wf = await fetchWorkflow(request, instanceId, workflowId);
        if (wf.graph) {
          handleFromApiGraph(wf.graph, wf.invocations);
        } else {
          applyGraphWithSync({ nodes: [], connections: [] }, wf.invocations);
        }
      } catch (err: unknown) {
        setExecuteResult({
          success: false,
          error: err instanceof Error ? err.message : String(err),
        });
      }
    },
    [request, instanceId, handleFromApiGraph, applyGraphWithSync]
  );

  const handleWorkflowSelect = useCallback(
    (workflowId: string | null) => {
      setCurrentWorkflowId(workflowId);
      if (workflowId) handleLoad(workflowId);
      else {
        setExecuteResult(null);
        applyGraphWithSync({ nodes: [], connections: [] }, _buildDefaultInvocations(t('Jetzt ausführen')));
      }
    },
    [handleLoad, applyGraphWithSync, t]
  );

  const handleNew = useCallback(() => {
    setCurrentWorkflowId(null);
    setExecuteResult(null);
    applyGraphWithSync({ nodes: [], connections: [] }, _buildDefaultInvocations(t('Jetzt ausführen')));
  }, [applyGraphWithSync, t]);

  const handleNodeParametersChange = useCallback((nodeId: string, parameters: Record<string, unknown>) => {
    setCanvasNodes((prev) =>
      prev.map((n) => {
        if (n.id !== nodeId) return n;
        const next = { ...n, parameters };
        if (n.type === 'flow.switch' && 'cases' in parameters) {
          const cases = (parameters.cases as unknown[]) ?? [];
          next.outputs = Math.max(1, cases.length);
        }
        return next;
      })
    );
  }, []);

  const handleMergeNodeParameters = useCallback((nodeId: string, patch: Record<string, unknown>) => {
    setCanvasNodes((prev) =>
      prev.map((n) => {
        if (n.id !== nodeId) return n;
        const merged = { ...(n.parameters ?? {}), ...patch };
        const next = { ...n, parameters: merged };
        if (n.type === 'flow.switch' && 'cases' in merged) {
          const cases = (merged.cases as unknown[]) ?? [];
          next.outputs = Math.max(1, cases.length);
        }
        return next;
      })
    );
  }, []);

  const handleNodeUpdate = useCallback(
    (nodeId: string, updates: Partial<Pick<CanvasNode, 'title' | 'comment'>>) => {
      setCanvasNodes((prev) =>
        prev.map((n) => (n.id === nodeId ? { ...n, ...updates } : n))
      );
    },
    []
  );

  const handleApplyWorkflowConfiguration = useCallback(
    (next: WorkflowEntryPoint[]) => {
      setInvocations(next);
      setCanvasNodes((nodes) => {
        const r = syncCanvasStartNode(nodes, canvasConnections, next, nodeTypes, language);
        setCanvasConnections(r.connections);
        return r.nodes;
      });
    },
    [canvasConnections, nodeTypes, language]
  );

  const loadNodeTypes = useCallback(async () => {
    if (!instanceId) return;
    setLoading(true);
    setError(null);
    try {
      const data = await fetchNodeTypes(request, instanceId, language);
      setNodeTypes(data.nodeTypes);
      setCategories(data.categories);
      if (data.portTypeCatalog) {
        setPortTypeCatalog(data.portTypeCatalog);
        setRegistryCatalog(data.portTypeCatalog as never);
      }
      if (data.systemVariables) setSystemVariables(data.systemVariables);
    } catch (err: unknown) {
      setError(err instanceof Error ? err.message : String(err));
      setNodeTypes([]);
      setCategories([]);
    } finally {
      setLoading(false);
    }
  }, [instanceId, language, request]);

  const loadWorkflows = useCallback(async () => {
    if (!instanceId) return;
    try {
      const result = await fetchWorkflows(request, instanceId);
      setWorkflows(Array.isArray(result) ? result : result.items);
    } catch (e) {
      console.error(`${LOG} loadWorkflows failed`, e);
    }
  }, [instanceId, request]);

  useEffect(() => {
    loadNodeTypes();
  }, [loadNodeTypes]);

  useEffect(() => {
    loadWorkflows();
  }, [loadWorkflows]);

  const lastAppliedInitialRef = useRef<string | null | undefined>(undefined);
  useEffect(() => {
    if (!initialWorkflowId || workflows.length === 0 || nodeTypes.length === 0) return;
    if (lastAppliedInitialRef.current === initialWorkflowId) return;
    lastAppliedInitialRef.current = initialWorkflowId;
    handleWorkflowSelect(initialWorkflowId);
  }, [initialWorkflowId, workflows, handleWorkflowSelect, nodeTypes.length]);

  useEffect(() => {
    if (loading || nodeTypes.length === 0) return;
    if (currentWorkflowId || initialWorkflowId) return;
    if (canvasNodes.length > 0) return;
    applyGraphWithSync({ nodes: [], connections: [] }, _buildDefaultInvocations(t('Jetzt ausführen')));
  }, [
    loading,
    nodeTypes.length,
    currentWorkflowId,
    initialWorkflowId,
    canvasNodes.length,
    applyGraphWithSync,
    t,
  ]);

  const toggleCategory = useCallback((id: string) => {
    setExpandedCategories((prev) => {
      const next = new Set(prev);
      if (next.has(id)) next.delete(id);
      else next.add(id);
      return next;
    });
  }, []);

  const handleDropNodeType = useCallback(
    (nodeTypeId: string, x: number, y: number) => {
      if (nodeTypeId.startsWith('trigger.')) return;
      const nt = nodeTypes.find((n) => n.id === nodeTypeId);
      if (!nt) return;
      const id = `n_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
      const label =
        typeof nt.label === 'string' ? nt.label : (nt.label as Record<string, string>)?.[language] ?? nt.id;
      setCanvasNodes((prev) => [
        ...prev,
        {
          id,
          type: nodeTypeId,
          x,
          y,
          label,
          title: label,
          color: nt.meta?.color,
          inputs: nt.inputs ?? 1,
          outputs: nt.outputs ?? 1,
          parameters: {},
        },
      ]);
    },
    [nodeTypes, language]
  );

  const loadVersions = useCallback(async () => {
    if (!instanceId || !currentWorkflowId) {
      setVersions([]);
      return;
    }
    try {
      const v = await fetchVersions(request, instanceId, currentWorkflowId);
      setVersions(v);
    } catch (e) {
      console.error(`${LOG} loadVersions failed`, e);
    }
  }, [instanceId, currentWorkflowId, request]);

  useEffect(() => {
    loadVersions();
  }, [loadVersions]);

  const handleVersionSelect = useCallback(
    (versionId: string | null) => {
      setCurrentVersionId(versionId);
      if (versionId) {
        const v = versions.find((ver) => ver.id === versionId);
        if (v?.graph) {
          handleFromApiGraph(v.graph, v.invocations);
        }
      }
    },
    [versions, handleFromApiGraph]
  );

  const handlePublishVersion = useCallback(
    async (versionId: string) => {
      if (!instanceId) return;
      setVersionLoading(true);
      try {
        await publishVersion(request, instanceId, versionId);
        await loadVersions();
      } catch (e: unknown) {
        setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
      } finally {
        setVersionLoading(false);
      }
    },
    [request, instanceId, loadVersions]
  );

  const handleUnpublishVersion = useCallback(
    async (versionId: string) => {
      if (!instanceId) return;
      setVersionLoading(true);
      try {
        await unpublishVersion(request, instanceId, versionId);
        await loadVersions();
      } catch (e: unknown) {
        setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
      } finally {
        setVersionLoading(false);
      }
    },
    [request, instanceId, loadVersions]
  );

  const handleArchiveVersion = useCallback(
    async (versionId: string) => {
      if (!instanceId) return;
      setVersionLoading(true);
      try {
        await archiveVersion(request, instanceId, versionId);
        await loadVersions();
      } catch (e: unknown) {
        setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
      } finally {
        setVersionLoading(false);
      }
    },
    [request, instanceId, loadVersions]
  );

  const handleCreateDraft = useCallback(async () => {
    if (!instanceId || !currentWorkflowId) return;
    setVersionLoading(true);
    try {
      const draft = await createDraftVersion(request, instanceId, currentWorkflowId);
      await loadVersions();
      setCurrentVersionId(draft.id);
    } catch (e: unknown) {
      setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
    } finally {
      setVersionLoading(false);
    }
  }, [request, instanceId, currentWorkflowId, loadVersions]);

  // Template: save current workflow as template
  const [templateSaving, setTemplateSaving] = useState(false);
  const handleSaveAsTemplate = useCallback(
    async (scope: AutoTemplateScope) => {
      if (!instanceId || !currentWorkflowId) return;
      setTemplateSaving(true);
      try {
        await createTemplateFromWorkflow(request, instanceId, currentWorkflowId, scope);
        setExecuteResult({ success: true, error: undefined } as unknown as ExecuteGraphResponse);
      } catch (e: unknown) {
        setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
      } finally {
        setTemplateSaving(false);
      }
    },
    [request, instanceId, currentWorkflowId]
  );

  // Template: new workflow from template
  const [templatePickerOpen, setTemplatePickerOpen] = useState(false);
  const handleNewFromTemplate = useCallback(
    async (templateId: string) => {
      if (!instanceId) return;
      try {
        const wf = await copyTemplate(request, instanceId, templateId);
        setWorkflows((prev) => [...prev, wf]);
        setCurrentWorkflowId(wf.id);
        if (wf.graph) handleFromApiGraph(wf.graph, wf.invocations);
        setTemplatePickerOpen(false);
      } catch (e: unknown) {
        setExecuteResult({ success: false, error: e instanceof Error ? e.message : String(e) });
      }
    },
    [request, instanceId, handleFromApiGraph]
  );

  const handleWorkflowRename = useCallback(async (workflowId: string, newName: string) => {
    try {
      await updateWorkflow(request, instanceId, workflowId, { label: newName });
      setWorkflows((prev) => prev.map((w) => w.id === workflowId ? { ...w, label: newName } : w));
    } catch (e: unknown) {
      console.error(`${LOG} rename failed`, e);
    }
  }, [request, instanceId]);

  const _sidebarStyle = useMemo(() => ({ width: sidebarWidth }), [sidebarWidth]);

  const renderSidebar = () => {
    if (loading) {
      return (
        <div className={styles.sidebar} style={_sidebarStyle}>
          <div className={styles.sidebarHeader}>
            <h3 className={styles.sidebarTitle}>{t('Knoten')}</h3>
          </div>
          <div className={styles.loading}>
            <FaSpinner className={styles.spinner} style={{ marginBottom: '0.5rem' }} />
            <p>{t('Lade Nodetypen…')}</p>
          </div>
        </div>
      );
    }
    if (error) {
      return (
        <div className={styles.sidebar} style={_sidebarStyle}>
          <div className={styles.sidebarHeader}>
            <h3 className={styles.sidebarTitle}>{t('Knoten')}</h3>
          </div>
          <div className={styles.error}>
            <p>{error}</p>
            <button className={styles.retryButton} onClick={loadNodeTypes}>
              {t('Erneut versuchen')}
            </button>
          </div>
        </div>
      );
    }
    return (
      <NodeSidebar
        nodeTypes={nodeTypes}
        categories={categories}
        filter={filter}
        onFilterChange={setFilter}
        language={language}
        expandedCategories={expandedCategories}
        onToggleCategory={toggleCategory}
        excludedCategories={sidebarExcludedCategories}
        style={_sidebarStyle}
      />
    );
  };

  const configurableSelected =
    selectedNode &&
    ['input.', 'ai.', 'email.', 'sharepoint.', 'clickup.', 'trigger.', 'flow.', 'file.', 'trustee.'].some((p) =>
      selectedNode.type.startsWith(p)
    );

  return (
    <div className={styles.container}>
      {/* Left panel: Workspace (Chats / Dateien / Quellen) */}
      {leftPanelOpen && (<>
        <div style={{ width: leftPanelWidth, flexShrink: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden', background: 'var(--bg-primary, #fff)' }}>
          <div className={styles.rightTabBar}>
            {(['chats', 'files', 'sources'] as const).map((tab) => (
              <button
                key={tab}
                className={`${styles.rightTab} ${udbTab === tab ? styles.rightTabActive : ''}`}
                onClick={() => setUdbTab(tab)}
              >
                {{ chats: t('Chats'), files: t('Dateien'), sources: t('Quellen') }[tab]}
              </button>
            ))}
          </div>
          <div style={{ flex: 1, overflow: 'hidden' }}>
            {udbTab === 'chats' ? (
              <EditorChatPanel
                instanceId={instanceId}
                workflowId={currentWorkflowId}
                onGraphUpdated={() => { if (currentWorkflowId) handleLoad(currentWorkflowId); }}
                pendingFiles={pendingFiles}
                onRemovePendingFile={onRemovePendingFile}
                dataSources={dataSources}
                featureDataSources={featureDataSources}
              />
            ) : (
              <UnifiedDataBar
                context={udbContext}
                activeTab={udbTab}
                onTabChange={setUdbTab}
                hideTabs={['chats']}
                onFileSelect={onFileSelect}
                onSourcesChanged={onSourcesChanged}
              />
            )}
          </div>
        </div>
        <div className={styles.resizeDivider} onMouseDown={(e) => _startResize('left', e)} />
      </>)}

      {/* Canvas area - center */}
      <div className={styles.canvas}>
        <CanvasHeader
          workflows={workflows}
          currentWorkflowId={currentWorkflowId}
          onWorkflowSelect={handleWorkflowSelect}
          onNew={handleNew}
          onSave={handleSave}
          onExecute={handleExecute}
          onWorkflowSettings={() => setWorkflowSettingsOpen(true)}
          onToggleChat={() => setLeftPanelOpen((prev) => !prev)}
          saving={saving}
          executing={executing}
          hasNodes={canvasNodes.length > 0}
          executeResult={executeResult}
          versions={versions}
          currentVersionId={currentVersionId}
          onVersionSelect={handleVersionSelect}
          onPublishVersion={handlePublishVersion}
          onUnpublishVersion={handleUnpublishVersion}
          onArchiveVersion={handleArchiveVersion}
          onCreateDraft={handleCreateDraft}
          versionLoading={versionLoading}
          onSaveAsTemplate={handleSaveAsTemplate}
          templateSaving={templateSaving}
          onNewFromTemplate={() => setTemplatePickerOpen(true)}
          onWorkflowRename={handleWorkflowRename}
        />
        <div className={styles.canvasArea} style={{ display: 'flex', flex: 1, minWidth: 0 }}>
          <div style={{ flex: 1, minWidth: 0 }}>
            <FlowCanvas
              nodes={canvasNodes}
              connections={canvasConnections}
              nodeTypes={nodeTypes}
              onNodesChange={setCanvasNodes}
              onConnectionsChange={setCanvasConnections}
              onDropNodeType={handleDropNodeType}
              getLabel={(node) => node.title ?? node.label ?? node.type}
              getCategoryIcon={getCategoryIcon}
              onSelectionChange={setSelectedNode}
              highlightedNodeIds={tracingRunId ? tracingNodeStatuses : undefined}
            />
          </div>
          {configurableSelected && selectedNode && (
            <Automation2DataFlowProvider
              node={selectedNode}
              nodes={canvasNodes}
              connections={canvasConnections}
              nodeOutputsPreview={nodeOutputsPreview}
              nodeTypes={nodeTypes}
              language={language}
              portTypeCatalog={portTypeCatalog as Record<string, never>}
              systemVariables={systemVariables as Record<string, never>}
            >
              <NodeConfigPanel
                node={selectedNode}
                nodeType={nodeTypes.find((nt) => nt.id === selectedNode.type)}
                language={language}
                onParametersChange={handleNodeParametersChange}
                onMergeNodeParameters={handleMergeNodeParameters}
                onNodeUpdate={handleNodeUpdate}
                instanceId={instanceId}
                request={request}
              />
            </Automation2DataFlowProvider>
          )}
        </div>
      </div>

      {/* Right panel: Nodes + Tracing tabs */}
      <div className={styles.resizeDivider} onMouseDown={(e) => _startResize('right', e)} />
      <div style={{ width: sidebarWidth, flexShrink: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden', background: 'var(--bg-secondary, #f8f9fa)' }}>
        <div className={styles.rightTabBar}>
          <button
            className={`${styles.rightTab} ${rightTab === 'nodes' ? styles.rightTabActive : ''}`}
            onClick={() => setRightTab('nodes')}
          >
            {t('Knoten')}
          </button>
          <button
            className={`${styles.rightTab} ${rightTab === 'tracing' ? styles.rightTabActive : ''}`}
            onClick={() => { setRightTab('tracing'); if (!tracingRunId) setTracingRunId('select'); }}
          >
            {t('Ablaufverfolgung')}
          </button>
        </div>
        <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0 }}>
          {rightTab === 'nodes' ? (
            renderSidebar()
          ) : (
            <RunTracingPanel
              instanceId={instanceId}
              runId={tracingRunId === 'select' ? null : tracingRunId}
              onNodeSelect={(nodeId) => {
                const node = canvasNodes.find((n) => n.id === nodeId);
                if (node) setSelectedNode(node);
              }}
              onActiveStepsChange={setTracingNodeStatuses}
            />
          )}
        </div>
      </div>

      <PromptDialog />
      <WorkflowConfigurationModal
        open={workflowSettingsOpen}
        onClose={() => setWorkflowSettingsOpen(false)}
        invocations={invocations}
        onApply={handleApplyWorkflowConfiguration}
      />
      <TemplatePicker
        open={templatePickerOpen}
        onClose={() => setTemplatePickerOpen(false)}
        onSelect={handleNewFromTemplate}
        instanceId={instanceId}
        request={request}
      />
    </div>
  );
};

export default Automation2FlowEditor;
