'use client'

import type { FC } from 'react'
import type { Viewport } from 'reactflow'
import type { CursorPosition, OnlineUser } from './collaboration/types/collaboration'
import type { Shape as HooksStoreShape } from './hooks-store'
import type { WorkflowSliceShape } from './store/workflow/workflow-slice'
import type { ConversationVariable, Edge, EnvironmentVariable, Node } from './types'
import type { EventEmitterValue } from '@/context/event-emitter'
import type { VarInInspect } from '@/types/workflow'
import {
  AlertDialog,
  AlertDialogActions,
  AlertDialogCancelButton,
  AlertDialogConfirmButton,
  AlertDialogContent,
  AlertDialogDescription,
  AlertDialogTitle,
} from '@langgenius/dify-ui/alert-dialog'
import { cn } from '@langgenius/dify-ui/cn'
import { toast } from '@langgenius/dify-ui/toast'
import { useEventListener } from 'ahooks'
import { isEqual } from 'es-toolkit/predicate'
import { setAutoFreeze } from 'immer'
import {
  Fragment,
  memo,
  Suspense,
  useCallback,
  useEffect,
  useEffectEvent,
  useMemo,
  useRef,
  useState,
} from 'react'
import { useTranslation } from 'react-i18next'
import ReactFlow, {
  Background,
  ReactFlowProvider,
  SelectionMode,
  useEdgesState,
  useNodes,
  useNodesState,
  useOnViewportChange,
  useReactFlow,
  useStoreApi,
} from 'reactflow'
import { IS_DEV } from '@/config'
import { useEventEmitterContextContext } from '@/context/event-emitter'
import {
  useAllBuiltInTools,
  useAllCustomTools,
  useAllMCPTools,
  useAllWorkflowTools,
} from '@/service/use-tools'
import { fetchAllInspectVars } from '@/service/workflow'
import CandidateNode from './candidate-node'
import UserCursors from './collaboration/components/user-cursors'
import { collaborationManager } from './collaboration/core/collaboration-manager'
import CommentManager from './comment-manager'
import { CommentIcon } from './comment/comment-icon'
import { CommentInput } from './comment/comment-input'
import { CommentCursor } from './comment/cursor'
import { CommentThread } from './comment/thread'
import { CUSTOM_EDGE, CUSTOM_NODE, WORKFLOW_DATA_UPDATE } from './constants'
import CustomConnectionLine from './custom-connection-line'
import CustomEdge from './custom-edge'
import DatasetsDetailProvider from './datasets-detail-store/provider'
import HelpLine from './help-line'
import { HooksStoreContextProvider, useHooksStore } from './hooks-store'
import { useEdgesInteractions } from './hooks/use-edges-interactions'
import { useLocateNode } from './hooks/use-locate-node'
import { useNodesInteractions } from './hooks/use-nodes-interactions'
import { useNodesSyncDraft } from './hooks/use-nodes-sync-draft'
import { usePanelInteractions } from './hooks/use-panel-interactions'
import { useSelectionInteractions } from './hooks/use-selection-interactions'
import { useSetWorkflowVarsWithValue } from './hooks/use-set-workflow-vars-with-value'
import { useNodesReadOnly, useWorkflow, useWorkflowReadOnly } from './hooks/use-workflow'
import { useWorkflowComment } from './hooks/use-workflow-comment'
import { useWorkflowRefreshDraft } from './hooks/use-workflow-refresh-draft'
import { useWorkflowSearch } from './hooks/use-workflow-search'
import { shouldPreventWorkflowBrowserDefault } from './hotkeys'
import CustomNode from './nodes'
import useMatchSchemaType from './nodes/_base/components/variable/use-match-schema-type'
import CustomDataSourceEmptyNode from './nodes/data-source-empty'
import { CUSTOM_DATA_SOURCE_EMPTY_NODE } from './nodes/data-source-empty/constants'
import CustomIterationStartNode from './nodes/iteration-start'
import { CUSTOM_ITERATION_START_NODE } from './nodes/iteration-start/constants'
import CustomLoopStartNode from './nodes/loop-start'
import { CUSTOM_LOOP_START_NODE } from './nodes/loop-start/constants'
import CustomNoteNode from './note-node'
import { CUSTOM_NOTE_NODE } from './note-node/constants'
import Operator from './operator'
import Control from './operator/control'
import { WorkflowLocalStorageBridge } from './persistence/local-storage-bridge'
import { useWorkflowHotkeys } from './shortcuts/use-workflow-hotkeys'
import CustomSimpleNode from './simple-node'
import { CUSTOM_SIMPLE_NODE } from './simple-node/constants'
import { useStore, useWorkflowStore } from './store/workflow'
import SyncingDataModal from './syncing-data-modal'
import { ControlMode, WorkflowRunningStatus } from './types'
import { setupScrollToNodeListener } from './utils/node-navigation'
import { WorkflowContextmenu } from './workflow-contextmenu'
import 'reactflow/dist/style.css'
import './style.css'

const nodeTypes = {
  [CUSTOM_NODE]: CustomNode,
  [CUSTOM_NOTE_NODE]: CustomNoteNode,
  [CUSTOM_SIMPLE_NODE]: CustomSimpleNode,
  [CUSTOM_ITERATION_START_NODE]: CustomIterationStartNode,
  [CUSTOM_LOOP_START_NODE]: CustomLoopStartNode,
  [CUSTOM_DATA_SOURCE_EMPTY_NODE]: CustomDataSourceEmptyNode,
}
const edgeTypes = {
  [CUSTOM_EDGE]: CustomEdge,
}

type WorkflowDataUpdatePayload = {
  nodes: Node[]
  edges: Edge[]
  viewport?: Viewport
  hash?: string
  features?: unknown
  conversation_variables?: ConversationVariable[]
  environment_variables?: EnvironmentVariable[]
}

export type WorkflowProps = {
  nodes: Node[]
  edges: Edge[]
  viewport?: Viewport
  children?: React.ReactNode
  onWorkflowDataUpdate?: (v: WorkflowDataUpdatePayload) => void
  isCollaborationEnabled?: boolean
  cursors?: Record<string, CursorPosition>
  myUserId?: string | null
  onlineUsers?: OnlineUser[]
}

const CommentPlacementPreview = memo(
  ({
    onSubmit,
    onCancel,
  }: {
    onSubmit: (content: string, mentionedUserIds: string[]) => void
    onCancel: () => void
  }) => {
    const isCommentPlacing = useStore((s) => s.isCommentPlacing)
    const pendingComment = useStore((s) => s.pendingComment)
    const mousePosition = useStore((s) => s.mousePosition)

    if (!isCommentPlacing || pendingComment) return null

    return (
      <CommentInput
        position={{
          x: mousePosition.elementX,
          y: mousePosition.elementY,
        }}
        onSubmit={onSubmit}
        onCancel={onCancel}
        autoFocus={false}
        disabled
      />
    )
  },
)

CommentPlacementPreview.displayName = 'CommentPlacementPreview'

export const Workflow: FC<WorkflowProps> = memo(
  ({
    nodes: originalNodes,
    edges: originalEdges,
    viewport,
    children,
    onWorkflowDataUpdate,
    isCollaborationEnabled = false,
    cursors,
    myUserId,
    onlineUsers,
  }) => {
    const { t } = useTranslation()
    const workflowContainerRef = useRef<HTMLDivElement>(null)
    const workflowStore = useWorkflowStore()
    const reactflow = useReactFlow()
    const store = useStoreApi()
    const [isMouseOverCanvas, setIsMouseOverCanvas] = useState(false)
    const [nodes, setNodes] = useNodesState(originalNodes)
    const [edges, setEdges] = useEdgesState(originalEdges)
    const controlMode = useStore((s) => s.controlMode)
    const nodeAnimation = useStore((s) => s.nodeAnimation)
    const showConfirm = useStore((s) => s.showConfirm)
    const workflowCanvasHeight = useStore((s) => s.workflowCanvasHeight)
    const bottomPanelHeight = useStore((s) => s.bottomPanelHeight)
    const setWorkflowCanvasWidth = useStore((s) => s.setWorkflowCanvasWidth)
    const setWorkflowCanvasHeight = useStore((s) => s.setWorkflowCanvasHeight)
    const workflowCanvasSizeRef = useRef<{ width?: number; height?: number }>({})
    const workflowCanvasResizeFrameRef = useRef<number | undefined>(undefined)
    const controlHeight = useMemo(() => {
      if (!workflowCanvasHeight) return '100%'
      return workflowCanvasHeight - bottomPanelHeight
    }, [workflowCanvasHeight, bottomPanelHeight])

    // update workflow Canvas width and height
    useEffect(() => {
      if (workflowContainerRef.current) {
        const updateWorkflowCanvasSize = (width: number, height: number) => {
          if (
            workflowCanvasSizeRef.current.width === width &&
            workflowCanvasSizeRef.current.height === height
          )
            return

          workflowCanvasSizeRef.current = { width, height }
          if (workflowCanvasResizeFrameRef.current)
            cancelAnimationFrame(workflowCanvasResizeFrameRef.current)

          workflowCanvasResizeFrameRef.current = requestAnimationFrame(() => {
            workflowCanvasResizeFrameRef.current = undefined
            setWorkflowCanvasWidth(width)
            setWorkflowCanvasHeight(height)
          })
        }

        const resizeContainerObserver = new ResizeObserver((entries) => {
          for (const entry of entries) {
            const { inlineSize, blockSize } = entry.borderBoxSize[0]!
            updateWorkflowCanvasSize(inlineSize, blockSize)
          }
        })
        resizeContainerObserver.observe(workflowContainerRef.current)
        return () => {
          if (workflowCanvasResizeFrameRef.current) {
            cancelAnimationFrame(workflowCanvasResizeFrameRef.current)
            workflowCanvasResizeFrameRef.current = undefined
          }
          resizeContainerObserver.disconnect()
        }
      }
    }, [setWorkflowCanvasHeight, setWorkflowCanvasWidth])

    const {
      setShowConfirm,
      setControlPromptEditorRerenderKey,
      setSyncWorkflowDraftHash,
      setNodes: setNodesInStore,
    } = workflowStore.getState()
    const currentNodes = useNodes()
    const setNodesOnlyChangeWithData = useCallback(
      (nodes: Node[]) => {
        const nodesData = nodes.map((node) => ({
          id: node.id,
          data: node.data,
        }))
        const oldData = workflowStore.getState().nodes.map((node) => ({
          id: node.id,
          data: node.data,
        }))
        if (!isEqual(oldData, nodesData)) setNodesInStore(nodes)
      },
      [setNodesInStore, workflowStore],
    )
    useEffect(() => {
      setNodesOnlyChangeWithData(currentNodes as Node[])
    }, [currentNodes, setNodesOnlyChangeWithData])
    useEffect(() => {
      return collaborationManager.onGraphImport(
        ({ nodes: importedNodes, edges: importedEdges }) => {
          if (!isEqual(nodes, importedNodes)) {
            setNodes(importedNodes)
            store.getState().setNodes(importedNodes)
          }
          if (!isEqual(edges, importedEdges)) {
            setEdges(importedEdges)
            store.getState().setEdges(importedEdges)
          }
        },
      )
    }, [edges, nodes, setEdges, setNodes, store])

    useEffect(() => {
      return collaborationManager.onHistoryAction((_) => {
        toast.info(t(($) => $['collaboration.historyAction.generic'], { ns: 'workflow' }))
      })
    }, [t])

    useEffect(() => {
      return collaborationManager.onRestoreIntent((data) => {
        toast.info(
          t(($) => $['versionHistory.action.restoreInProgress'], {
            ns: 'workflow',
            userName: data.initiatorName,
            versionName: data.versionName || data.versionId,
          }),
        )
      })
    }, [t])

    const { handleSyncWorkflowDraft, syncWorkflowDraftWhenPageClose } = useNodesSyncDraft()
    const { workflowReadOnly } = useWorkflowReadOnly()
    const { nodesReadOnly } = useNodesReadOnly()
    const { eventEmitter } = useEventEmitterContextContext()
    const {
      comments,
      pendingComment,
      activeComment,
      activeCommentLoading,
      replySubmitting,
      replyUpdating,
      handleCommentSubmit,
      handleCommentCancel,
      handleCommentIconClick,
      handleActiveCommentClose,
      handleCommentResolve,
      handleCommentDelete,
      handleCommentUpdate,
      handleCommentReply,
      handleCommentReplyUpdate,
      handleCommentReplyDelete,
      handleCommentPositionUpdate,
    } = useWorkflowComment()
    const showUserComments = useStore((s) => s.showUserComments)
    const showUserCursors = useStore((s) => s.showUserCursors)
    const showResolvedComments = useStore((s) => s.showResolvedComments)
    const isCommentPreviewHovering = useStore((s) => s.isCommentPreviewHovering)
    const isCommentPlacing = useStore((s) => s.isCommentPlacing)
    const setCommentPlacing = useStore((s) => s.setCommentPlacing)
    const setCommentQuickAdd = useStore((s) => s.setCommentQuickAdd)
    const setPendingCommentState = useStore((s) => s.setPendingComment)
    const isCommentInputActive = Boolean(pendingComment) || isCommentPlacing
    const visibleComments = useMemo(() => {
      if (showResolvedComments) return comments
      return comments.filter((comment) => !comment.resolved)
    }, [comments, showResolvedComments])
    const handleVisibleCommentNavigate = useCallback(
      (direction: 'prev' | 'next') => {
        if (!activeComment) return
        const idx = visibleComments.findIndex((comment) => comment.id === activeComment.id)
        if (idx === -1) return
        const target = direction === 'prev' ? visibleComments[idx - 1] : visibleComments[idx + 1]
        if (target) handleCommentIconClick(target)
      },
      [activeComment, handleCommentIconClick, visibleComments],
    )

    eventEmitter?.useSubscription((v: EventEmitterValue) => {
      if (typeof v === 'object' && v.type === WORKFLOW_DATA_UPDATE) {
        const payload = v.payload as WorkflowDataUpdatePayload
        setNodes(payload.nodes)
        store.getState().setNodes(payload.nodes)
        setEdges(payload.edges)
        workflowStore.setState({ contextMenuTarget: undefined })

        if (payload.viewport) reactflow.setViewport(payload.viewport)

        if (payload.hash) setSyncWorkflowDraftHash(payload.hash)

        onWorkflowDataUpdate?.(payload)

        setTimeout(() => setControlPromptEditorRerenderKey(Date.now()))
      }
    })

    useEffect(() => {
      setAutoFreeze(false)

      return () => {
        setAutoFreeze(true)
      }
    }, [])

    const syncWorkflowDraftOnUnmount = useEffectEvent(() => {
      if (!workflowStore.getState().isWorkflowDataLoaded) return

      if (isCollaborationEnabled && collaborationManager.canUseLocalDraftFallback()) {
        syncWorkflowDraftWhenPageClose()
        return
      }

      if (isCollaborationEnabled && !collaborationManager.canFlushGraphOnPageClose()) return

      handleSyncWorkflowDraft(true, true, {
        onError: () => {
          toast.error(
            t(($) => $['common.draftSaveFailed'], { ns: 'workflow' }),
            {
              timeout: 0,
            },
          )
        },
      })
    })
    useEffect(() => {
      return () => {
        syncWorkflowDraftOnUnmount()
      }
    }, [])

    const handlePendingCommentPositionChange = useCallback(
      (position: NonNullable<WorkflowSliceShape['pendingComment']>) => {
        setPendingCommentState(position)
      },
      [setPendingCommentState],
    )

    const handleCommentPlacementCancel = useCallback(() => {
      setPendingCommentState(null)
      setCommentPlacing(false)
      setCommentQuickAdd(false)
    }, [setCommentPlacing, setCommentQuickAdd, setPendingCommentState])

    const { handleRefreshWorkflowDraft } = useWorkflowRefreshDraft()
    const handleSyncWorkflowDraftWhenPageClose = useCallback(() => {
      if (document.visibilityState === 'hidden') {
        // Update the local guard synchronously. Waiting for the server's leader
        // status would leave a window where this hidden tab saves a stale canvas.
        collaborationManager.emitGraphViewState(false)
        syncWorkflowDraftWhenPageClose()
        return
      }

      if (document.visibilityState === 'visible') {
        collaborationManager.emitGraphViewState(true)
        const { isListening, workflowRunningData } = workflowStore.getState()
        const status = workflowRunningData?.result?.status
        // Avoid resetting UI state when user comes back while a run is active or listening for triggers
        if (isListening || status === WorkflowRunningStatus.Running) return

        // While this tab was hidden the canvas was frozen (rAF paused), but the CRDT doc kept
        // receiving remote edits. Restore from the CRDT instead of the DB draft — the DB may
        // hold the stale snapshot this very tab saved while hidden, and re-importing it would
        // broadcast a rollback to everyone. A trusted CRDT remains authoritative when empty.
        const collaborationConnected = collaborationManager.isConnected()
        if (collaborationConnected && !collaborationManager.canRestoreGraphFromCrdt()) return

        if (collaborationConnected) {
          collaborationManager.refreshGraphSynchronously()
          setTimeout(() => handleRefreshWorkflowDraft(true), 500)
        } else {
          setTimeout(() => handleRefreshWorkflowDraft(), 500)
        }
      }
    }, [syncWorkflowDraftWhenPageClose, handleRefreshWorkflowDraft, workflowStore])

    // Also add beforeunload handler as additional safety net for tab close
    const handleBeforeUnload = useCallback(() => {
      if (collaborationManager.canRestoreGraphFromCrdt())
        collaborationManager.refreshGraphSynchronously()
      syncWorkflowDraftWhenPageClose()
    }, [syncWorkflowDraftWhenPageClose])

    // Optimized comment deletion using showConfirm
    const handleCommentDeleteClick = useCallback(
      (commentId: string) => {
        if (!showConfirm) {
          setShowConfirm({
            title: t(($) => $['comments.confirm.deleteThreadTitle'], { ns: 'workflow' }),
            desc: t(($) => $['comments.confirm.deleteThreadDesc'], { ns: 'workflow' }),
            onConfirm: async () => {
              await handleCommentDelete(commentId)
              setShowConfirm(undefined)
            },
          })
        }
      },
      [showConfirm, setShowConfirm, handleCommentDelete, t],
    )

    const handleCommentReplyDeleteClick = useCallback(
      (commentId: string, replyId: string) => {
        if (!showConfirm) {
          setShowConfirm({
            title: t(($) => $['comments.confirm.deleteReplyTitle'], { ns: 'workflow' }),
            desc: t(($) => $['comments.confirm.deleteReplyDesc'], { ns: 'workflow' }),
            onConfirm: async () => {
              await handleCommentReplyDelete(commentId, replyId)
              setShowConfirm(undefined)
            },
          })
        }
      },
      [showConfirm, setShowConfirm, handleCommentReplyDelete, t],
    )

    useEffect(() => {
      document.addEventListener('visibilitychange', handleSyncWorkflowDraftWhenPageClose)
      window.addEventListener('beforeunload', handleBeforeUnload)

      return () => {
        document.removeEventListener('visibilitychange', handleSyncWorkflowDraftWhenPageClose)
        window.removeEventListener('beforeunload', handleBeforeUnload)
      }
    }, [handleSyncWorkflowDraftWhenPageClose, handleBeforeUnload])

    useEventListener('keydown', (e) => {
      if (shouldPreventWorkflowBrowserDefault(e)) e.preventDefault()
    })
    useEventListener('mousemove', (e) => {
      const containerClientRect = workflowContainerRef.current?.getBoundingClientRect()

      if (containerClientRect) {
        workflowStore.setState({
          mousePosition: {
            pageX: e.clientX,
            pageY: e.clientY,
            elementX: e.clientX - containerClientRect.left,
            elementY: e.clientY - containerClientRect.top,
          },
        })
        const target = e.target as HTMLElement
        const onPane = !!target?.closest('.react-flow__pane')
        setIsMouseOverCanvas(onPane)
      }
    })

    // Prevent browser zoom interactions from hijacking gestures meant for the workflow canvas
    useEffect(() => {
      const preventBrowserZoom = (event: WheelEvent) => {
        if (!isCommentPreviewHovering && !isCommentInputActive) return

        if (event.ctrlKey || event.metaKey) event.preventDefault()
      }

      const preventGestureZoom = (event: Event) => {
        if (!isCommentPreviewHovering && !isCommentInputActive) return

        event.preventDefault()
      }

      window.addEventListener('wheel', preventBrowserZoom, { passive: false })
      const gestureEvents: Array<'gesturestart' | 'gesturechange' | 'gestureend'> = [
        'gesturestart',
        'gesturechange',
        'gestureend',
      ]
      gestureEvents.forEach((eventName) => {
        window.addEventListener(eventName, preventGestureZoom, { passive: false })
      })

      return () => {
        window.removeEventListener('wheel', preventBrowserZoom)
        gestureEvents.forEach((eventName) => {
          window.removeEventListener(eventName, preventGestureZoom)
        })
      }
    }, [isCommentPreviewHovering, isCommentInputActive])

    const {
      handleNodeDragStart,
      handleNodeDrag,
      handleNodeDragStop,
      handleNodeEnter,
      handleNodeLeave,
      handleNodeClick,
      handleNodeConnect,
      handleNodeConnectStart,
      handleNodeConnectEnd,
      handleNodeContextMenu,
      handleHistoryBack,
      handleHistoryForward,
    } = useNodesInteractions()
    const { handleEdgeEnter, handleEdgeLeave, handleEdgesChange, handleEdgeContextMenu } =
      useEdgesInteractions()
    const {
      handleSelectionStart,
      handleSelectionChange,
      handleSelectionDrag,
      handleSelectionContextMenu,
    } = useSelectionInteractions()
    const { handlePaneContextMenu } = usePanelInteractions()
    const { isValidConnection } = useWorkflow()

    useOnViewportChange({
      onEnd: () => {
        handleSyncWorkflowDraft()
      },
    })

    useWorkflowHotkeys()
    // Initialize workflow node search functionality
    useWorkflowSearch()

    // Locate a node by ID from URL query parameter `node_id`
    useLocateNode(nodes)

    // Set up scroll to node event listener using the utility function
    useEffect(() => {
      return setupScrollToNodeListener(nodes, reactflow)
    }, [nodes, reactflow])

    const { schemaTypeDefinitions } = useMatchSchemaType()
    const { fetchInspectVars } = useSetWorkflowVarsWithValue()
    const { data: buildInTools } = useAllBuiltInTools()
    const { data: customTools } = useAllCustomTools()
    const { data: workflowTools } = useAllWorkflowTools()
    const { data: mcpTools } = useAllMCPTools()
    const dataSourceList = useStore((s) => s.dataSourceList)
    // buildInTools, customTools, workflowTools, mcpTools, dataSourceList
    const configsMap = useHooksStore((s) => s.configsMap)
    const [isLoadedVars, setIsLoadedVars] = useState(false)
    const [vars, setVars] = useState<VarInInspect[]>([])
    useEffect(() => {
      ;(async () => {
        if (!configsMap?.flowType || !configsMap?.flowId) return
        const data = await fetchAllInspectVars(configsMap.flowType, configsMap.flowId)
        setVars(data)
        setIsLoadedVars(true)
      })()
    }, [configsMap?.flowType, configsMap?.flowId])
    useEffect(() => {
      if (schemaTypeDefinitions && isLoadedVars) {
        fetchInspectVars({
          passInVars: true,
          vars,
          passedInAllPluginInfoList: {
            buildInTools: buildInTools || [],
            customTools: customTools || [],
            workflowTools: workflowTools || [],
            mcpTools: mcpTools || [],
            dataSourceList: dataSourceList ?? [],
          },
          passedInSchemaTypeDefinitions: schemaTypeDefinitions,
        })
      }
    }, [
      schemaTypeDefinitions,
      fetchInspectVars,
      isLoadedVars,
      vars,
      customTools,
      buildInTools,
      workflowTools,
      mcpTools,
      dataSourceList,
    ])

    if (IS_DEV) {
      store.getState().onError = (code, message) => {
        if (code === '002') return
        console.warn(message)
      }
    }

    return (
      <div
        id="workflow-container"
        className={cn(
          'relative isolate h-full w-full min-w-[960px] overflow-hidden',
          workflowReadOnly && 'workflow-panel-animation',
          nodeAnimation && 'workflow-node-animation',
        )}
        ref={workflowContainerRef}
      >
        <CandidateNode />
        <CommentManager />
        <div
          className="pointer-events-none absolute top-0 left-0 z-10 flex w-12 items-center justify-center p-1 pl-2"
          style={{ height: controlHeight }}
        >
          <Control />
        </div>
        <Operator handleRedo={handleHistoryForward} handleUndo={handleHistoryBack} />
        <HelpLine />
        <AlertDialog
          open={!!showConfirm}
          onOpenChange={(open) => !open && setShowConfirm(undefined)}
        >
          <AlertDialogContent>
            <div className="flex flex-col gap-2 px-6 pt-6 pb-4">
              <AlertDialogTitle className="w-full truncate title-2xl-semi-bold text-text-primary">
                {showConfirm?.title}
              </AlertDialogTitle>
              {showConfirm?.desc && (
                <AlertDialogDescription className="w-full system-md-regular wrap-break-word whitespace-pre-wrap text-text-tertiary">
                  {showConfirm.desc}
                </AlertDialogDescription>
              )}
            </div>
            <AlertDialogActions>
              <AlertDialogCancelButton>
                {t(($) => $['operation.cancel'], { ns: 'common' })}
              </AlertDialogCancelButton>
              <AlertDialogConfirmButton onClick={showConfirm?.onConfirm}>
                {t(($) => $['operation.confirm'], { ns: 'common' })}
              </AlertDialogConfirmButton>
            </AlertDialogActions>
          </AlertDialogContent>
        </AlertDialog>
        {controlMode === ControlMode.Comment && isMouseOverCanvas && <CommentCursor />}
        <CommentPlacementPreview
          onSubmit={handleCommentSubmit}
          onCancel={handleCommentPlacementCancel}
        />
        {pendingComment && (
          <CommentInput
            position={{
              x: pendingComment.elementX,
              y: pendingComment.elementY,
            }}
            onSubmit={handleCommentSubmit}
            onCancel={handleCommentCancel}
            onPositionChange={handlePendingCommentPositionChange}
          />
        )}
        {visibleComments.map((comment, index) => {
          const isActive = activeComment?.id === comment.id

          if (isActive && activeComment) {
            const canGoPrev = index > 0
            const canGoNext = index < visibleComments.length - 1
            return (
              <Fragment key={comment.id}>
                <CommentIcon
                  key={`${comment.id}-icon`}
                  comment={comment}
                  onClick={() => handleCommentIconClick(comment)}
                  isActive={true}
                  onPositionUpdate={(position) => handleCommentPositionUpdate(comment.id, position)}
                />
                <CommentThread
                  key={`${comment.id}-thread`}
                  comment={activeComment}
                  loading={activeCommentLoading}
                  replySubmitting={replySubmitting}
                  replyUpdating={replyUpdating}
                  onClose={handleActiveCommentClose}
                  onResolve={() => handleCommentResolve(comment.id)}
                  onDelete={() => handleCommentDeleteClick(comment.id)}
                  onCommentEdit={(content, ids) =>
                    handleCommentUpdate(comment.id, content, ids ?? [])
                  }
                  onPrev={canGoPrev ? () => handleVisibleCommentNavigate('prev') : undefined}
                  onNext={canGoNext ? () => handleVisibleCommentNavigate('next') : undefined}
                  onReply={(content, ids) => handleCommentReply(comment.id, content, ids ?? [])}
                  onReplyEdit={(replyId, content, ids) =>
                    handleCommentReplyUpdate(comment.id, replyId, content, ids ?? [])
                  }
                  onReplyDelete={(replyId) => handleCommentReplyDeleteClick(comment.id, replyId)}
                  onReplyDeleteDirect={(replyId) => handleCommentReplyDelete(comment.id, replyId)}
                  canGoPrev={canGoPrev}
                  canGoNext={canGoNext}
                />
              </Fragment>
            )
          }

          return showUserComments || controlMode === ControlMode.Comment ? (
            <CommentIcon
              key={comment.id}
              comment={comment}
              onClick={() => handleCommentIconClick(comment)}
              onPositionUpdate={(position) => handleCommentPositionUpdate(comment.id, position)}
            />
          ) : null
        })}
        {children}
        <WorkflowContextmenu>
          <ReactFlow
            nodeTypes={nodeTypes}
            edgeTypes={edgeTypes}
            nodes={nodes}
            edges={edges}
            className={controlMode === ControlMode.Comment ? 'comment-mode-flow' : ''}
            onNodeDragStart={handleNodeDragStart}
            onNodeDrag={handleNodeDrag}
            onNodeDragStop={handleNodeDragStop}
            onNodeMouseEnter={handleNodeEnter}
            onNodeMouseLeave={handleNodeLeave}
            onNodeClick={handleNodeClick}
            onNodeContextMenu={handleNodeContextMenu}
            onConnect={handleNodeConnect}
            onConnectStart={handleNodeConnectStart}
            onConnectEnd={handleNodeConnectEnd}
            onEdgeMouseEnter={handleEdgeEnter}
            onEdgeMouseLeave={handleEdgeLeave}
            onEdgesChange={handleEdgesChange}
            onEdgeContextMenu={handleEdgeContextMenu}
            onSelectionStart={handleSelectionStart}
            onSelectionChange={handleSelectionChange}
            onSelectionDrag={handleSelectionDrag}
            onPaneContextMenu={handlePaneContextMenu}
            onSelectionContextMenu={handleSelectionContextMenu}
            connectionLineComponent={CustomConnectionLine}
            defaultViewport={viewport}
            multiSelectionKeyCode={null}
            deleteKeyCode={null}
            nodesDraggable={!nodesReadOnly && controlMode !== ControlMode.Comment}
            nodesConnectable={!nodesReadOnly}
            nodesFocusable={!nodesReadOnly}
            edgesFocusable={!nodesReadOnly}
            panOnScroll={controlMode === ControlMode.Pointer && !workflowReadOnly}
            panOnDrag={controlMode === ControlMode.Hand || [1]}
            zoomOnPinch={true}
            zoomOnScroll={true}
            zoomOnDoubleClick={true}
            isValidConnection={isValidConnection}
            selectionKeyCode={null}
            selectionMode={SelectionMode.Partial}
            selectionOnDrag={controlMode === ControlMode.Pointer && !workflowReadOnly}
            minZoom={0.25}
          >
            <Background
              gap={[14, 14]}
              size={2}
              className="bg-workflow-canvas-workflow-bg"
              color="var(--color-workflow-canvas-workflow-dot-color)"
            />
            {showUserCursors && cursors && (
              <UserCursors
                cursors={cursors}
                myUserId={myUserId || null}
                onlineUsers={onlineUsers || []}
              />
            )}
          </ReactFlow>
        </WorkflowContextmenu>
        <SyncingDataModal />
      </div>
    )
  },
)

type WorkflowWithInnerContextProps = WorkflowProps & {
  hooksStore?: Partial<HooksStoreShape>
  cursors?: Record<string, CursorPosition>
  myUserId?: string | null
  onlineUsers?: OnlineUser[]
}
export const WorkflowWithInnerContext = memo(
  ({ hooksStore, cursors, myUserId, onlineUsers, ...restProps }: WorkflowWithInnerContextProps) => {
    return (
      <HooksStoreContextProvider {...hooksStore}>
        <Workflow {...restProps} cursors={cursors} myUserId={myUserId} onlineUsers={onlineUsers} />
      </HooksStoreContextProvider>
    )
  },
)

type WorkflowWithDefaultContextProps = Pick<WorkflowProps, 'edges' | 'nodes'> & {
  children: React.ReactNode
}

const WorkflowHistoryStoreInitializer = ({
  nodes,
  edges,
  children,
}: WorkflowWithDefaultContextProps) => {
  const workflowStore = useWorkflowStore()
  const initializedRef = useRef(false)

  if (!initializedRef.current) {
    workflowStore.temporal.getState().pause()
    workflowStore.getState().setWorkflowHistory({
      nodes,
      edges,
      workflowHistoryEvent: undefined,
      workflowHistoryEventMeta: undefined,
    })
    workflowStore.temporal.getState().clear()
    workflowStore.temporal.getState().resume()
    initializedRef.current = true
  }

  return children
}

const WorkflowWithDefaultContext = ({
  nodes,
  edges,
  children,
}: WorkflowWithDefaultContextProps) => {
  return (
    <ReactFlowProvider>
      <WorkflowHistoryStoreInitializer nodes={nodes} edges={edges}>
        <Suspense fallback={null}>
          <WorkflowLocalStorageBridge />
        </Suspense>
        <DatasetsDetailProvider nodes={nodes}>{children}</DatasetsDetailProvider>
      </WorkflowHistoryStoreInitializer>
    </ReactFlowProvider>
  )
}

export default memo(WorkflowWithDefaultContext)
