<script lang="ts">
  import {
    Background,
    type Edge,
    type EdgeTypes,
    MarkerType,
    MiniMap,
    type Node,
    type NodeTypes,
    Panel,
    SvelteFlow,
    useSvelteFlow
  } from '@xyflow/svelte';
  import '@xyflow/svelte/dist/style.css';
  import '../styles/index.ts';
  import {store} from '#store/stores.svelte';
  import {nodeTypes} from './nodes';
  import Toolbar from './Toolbar.svelte';
  import NodePicker from './core/NodePicker.svelte';
  import FlowEdge from './core/FlowEdge.svelte';
  import FlowLinePath from './core/FlowLinePath.svelte';
  import FlowMarkerDefs from './core/FlowMarkerDefs.svelte';
  import FlowConnectionLine from './core/FlowConnectionLine.svelte';
  import {genShortId} from './utils/IdGen';
  import {useGetNode} from './utils/useGetNode.svelte';
  import {useEnsureParentInNodesBefore} from './utils/useEnsureParentInNodesBefore.svelte';
  import {Textarea} from './base';
  import {useGetEdgesByTarget} from './utils/useGetEdgesByTarget.svelte';
  import {getOptions} from './utils/NodeUtils';
  import CustomNode from './nodes/CustomNode.svelte';
  import {useUpdateEdgeData} from './utils/useUpdateEdgeData.svelte';
  import {Button} from '#components/base/index';
  import {useDeleteEdge} from '#components/utils/useDeleteEdge.svelte';
  import {useGetNodesFromSource} from '#components/utils/useGetNodesFromSource.svelte';
  import {useGetNodeRelativePosition} from '#components/utils/useGetNodeRelativePosition.svelte';
  import {useCopyPasteHandler} from '#components/utils/useCopyPasteHandler.svelte';
  import {onDestroy, onMount} from 'svelte';
  import {isInEditableElement} from '#components/utils/isInEditableElement';
  import {getAvailableNodes, type NodePaletteItem} from './utils/nodePalette';
  import {
    buildSequentialFieldBindingPatches,
    ensureStartNodeParameters,
    START_NODE_TYPE,
  } from '../utils/workflowNodeFields';

  const { onInit }: { onInit: any; [key: string]: any } = $props();
    const svelteFlow = useSvelteFlow();

    let showEdgePanel = $state(false);
    let currentEdge = $state<Edge | null>(null);
    let nodePickerVisible = $state(false);
    let pendingConnection = $state<null | {
        source: string;
        sourceHandle?: string;
        startClientX: number;
        startClientY: number;
        clientX: number;
        clientY: number;
    }>(null);
    let flowRootEl = $state<HTMLDivElement | null>(null);
    let inlineNodePickerEl = $state<HTMLDivElement | null>(null);
    let connectStartPoint = $state<{ x: number; y: number } | null>(null);
    const asString = (value: unknown) => (value == null ? '' : String(value));
    const options = getOptions();
    const readonly = options.readonly === true;
    let canvasLocked = $state(readonly);
    const hideBottomDock = options.hideBottomDock === true;
    const hideEdgePanel = options.hideEdgePanel === true;
    const hideMiniMap = options.hideMiniMap === true;
    const hideNodePicker = options.hideNodePicker === true;
    const nodesDraggable = options.nodesDraggable ?? !readonly;
    const nodesConnectable = options.nodesConnectable ?? !readonly;
    const elementsSelectable = options.elementsSelectable ?? !readonly;
    const dropEnabled = options.dropEnabled ?? !readonly;
    const connectionEnabled = nodesConnectable && !readonly;
    const availableNodes = getAvailableNodes(options);
    const onRunTest = options.onRunTest;

    const { updateEdgeData } = useUpdateEdgeData();
    const pendingParentRepairs = new Set<string>();

    function scheduleOrphanParentRepair(nodeId: string) {
        if (!nodeId || pendingParentRepairs.has(nodeId)) {
            return;
        }
        pendingParentRepairs.add(nodeId);
        queueMicrotask(() => {
            pendingParentRepairs.delete(nodeId);
            const currentNode = getNode(nodeId);
            const parentId = asString(currentNode?.parentId).trim();
            if (!currentNode || !parentId || getNode(parentId)) {
                return;
            }
            svelteFlow.updateNode(nodeId, {
                parentId: undefined
            });
        });
    }

    function getResolvedParentId(node: null | Node | undefined) {
        const parentId = asString(node?.parentId).trim();
        if (!parentId) {
            return undefined;
        }
        if (getNode(parentId)) {
            return parentId;
        }
        if (node?.id) {
            scheduleOrphanParentRepair(node.id);
        }
        return undefined;
    }

    function repairOrphanParentNodes() {
        store.getNodes().forEach((node) => {
            if (asString(node.parentId).trim() && !getResolvedParentId(node)) {
                scheduleOrphanParentRepair(node.id);
            }
        });
    }

    function getEventClientPosition(event: any) {
        if (typeof event?.clientX === 'number' && typeof event?.clientY === 'number') {
            return { x: event.clientX, y: event.clientY };
        }
        const touch = event?.changedTouches?.[0] || event?.touches?.[0];
        if (touch && typeof touch.clientX === 'number' && typeof touch.clientY === 'number') {
            return { x: touch.clientX, y: touch.clientY };
        }
        if (flowRootEl) {
            const rect = flowRootEl.getBoundingClientRect();
            return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
        }
        return { x: window.innerWidth / 2, y: window.innerHeight / 2 };
    }

    function ensureEdgeVisualDefaults(edge: any) {
        return {
            ...edge,
            type: edge.type || 'flow',
            markerEnd: edge.markerEnd || {
                type: MarkerType.ArrowClosed,
                width: 20,
                height: 20
            }
        };
    }

    function normalizeEdgeBeforeConnect(edge: any) {
        const sourceNode = edge.source ? getNode(edge.source) : null;
        const sourceHandle = edge.sourceHandle || '';
        const isConditionBranchEdge = sourceNode?.type === 'conditionNode'
            && typeof sourceHandle === 'string'
            && sourceHandle.startsWith('branch_');

        if (isConditionBranchEdge) {
            const branchId = sourceHandle.slice(7);
            const branches = (sourceNode?.data?.branches || []) as Array<any>;
            const branch = branches.find((item) => item?.id === branchId);
            const branchLabel = branch?.label || '条件分支';
            return ensureEdgeVisualDefaults({
                ...edge,
                id: genShortId(),
                data: {
                    ...((edge as any).data || {}),
                    managedByConditionNode: true,
                    branchId,
                    branchLabel,
                    condition: `matchedBranchId === '${branchId}'`
                }
            });
        }

        return ensureEdgeVisualDefaults({
            ...edge,
            id: edge.id || genShortId()
        });
    }

    function addNodeByPalette(
        nodeItem: NodePaletteItem,
        position: { x: number; y: number },
        connection?: { source: string; sourceHandle?: string }
    ) {
        const sourceNode = connection?.source ? getNode(connection.source) : undefined;
        const newNode = {
            id: `node_${genShortId()}`,
            position: { ...position },
            type: nodeItem.type,
            data: {
                title: nodeItem.title,
                description: nodeItem.description,
                ...(nodeItem.extra || {})
            }
        } as Node;

        if (newNode.type === START_NODE_TYPE) {
            newNode.data = {
                ...(newNode.data || {}),
                parameters: ensureStartNodeParameters((newNode.data?.parameters as Array<any>) || [])
            };
        }

        if (sourceNode) {
            if (connection?.sourceHandle === 'loop_handle') {
                newNode.parentId = sourceNode.id;
            } else if (sourceNode.parentId) {
                newNode.parentId = sourceNode.parentId;
            }

            if (newNode.parentId) {
                const { x, y } = getNodeRelativePosition(newNode.parentId);
                newNode.position = {
                    x: position.x - x,
                    y: position.y - y
                };
            }
        }

        store.addNode(newNode);
        store.selectNodeOnly(newNode.id);

        if (newNode.parentId) {
            ensureParentInNodesBefore(newNode.parentId, newNode.id);
        }

        if (sourceNode && connection?.source) {
            const edge = normalizeEdgeBeforeConnect({
                source: connection.source,
                sourceHandle: connection.sourceHandle || undefined,
                target: newNode.id
            });
            store.addEdge(edge as Edge);
        }

        applyAutoBindingsForNode(newNode.id);
    }

    function closeNodePicker() {
        nodePickerVisible = false;
        pendingConnection = null;
        connectStartPoint = null;
    }

    function handleToolbarSelectNode(nodeItem: NodePaletteItem) {
        const rect = flowRootEl?.getBoundingClientRect();
        const clientX = rect ? rect.left + rect.width / 2 : window.innerWidth / 2;
        const clientY = rect ? rect.top + rect.height / 2 - 80 : window.innerHeight / 2;
        const position = svelteFlow.screenToFlowPosition({ x: clientX, y: clientY });
        addNodeByPalette(nodeItem, position);
    }

    function handlePickerSelectNode(nodeItem: NodePaletteItem) {
        if (!pendingConnection) {
            handleToolbarSelectNode(nodeItem);
            closeNodePicker();
            return;
        }
        const position = svelteFlow.screenToFlowPosition({
            x: pendingConnection.clientX,
            y: pendingConnection.clientY
        });
        addNodeByPalette(nodeItem, position, {
            source: pendingConnection.source,
            sourceHandle: pendingConnection.sourceHandle
        });
        closeNodePicker();
    }

    const inlineNodePickerStyle = $derived.by(() => {
        if (!nodePickerVisible || !pendingConnection || !flowRootEl) {
            return '';
        }
        const rect = flowRootEl.getBoundingClientRect();
        const margin = 12;
        const width = Math.min(480, Math.max(300, rect.width - margin * 2));
        const maxHeight = Math.min(380, Math.max(240, rect.height - margin * 2));
        const anchorX = pendingConnection.clientX - rect.left;
        const anchorY = pendingConnection.clientY - rect.top;

        let left = anchorX + 14;
        if (left + width > rect.width - margin) {
            left = rect.width - width - margin;
        }
        if (left < margin) {
            left = margin;
        }

        let top = anchorY - 24;
        if (top + maxHeight > rect.height - margin) {
            top = rect.height - maxHeight - margin;
        }
        if (top < margin) {
            top = margin;
        }

        return `left:${left}px;top:${top}px;width:${width}px;max-height:${maxHeight}px;`;
    });

    const pendingConnectionLine = $derived.by(() => {
        if (!nodePickerVisible || !pendingConnection || !flowRootEl) {
            return null;
        }
        const rect = flowRootEl.getBoundingClientRect();
        const x1 = pendingConnection.startClientX - rect.left;
        const y1 = pendingConnection.startClientY - rect.top;
        const x2 = pendingConnection.clientX - rect.left;
        const y2 = pendingConnection.clientY - rect.top;
        const deltaX = x2 - x1;
        const c1x = x1 + Math.max(26, deltaX * 0.35);
        const c2x = x2 - Math.max(26, deltaX * 0.35);
        return {
            x1,
            y1,
            x2,
            y2,
            path: `M ${x1} ${y1} C ${c1x} ${y1}, ${c2x} ${y2}, ${x2} ${y2}`
        };
    });

    // 支持的缩放百分比选项
    const zoomOptions = [25, 50, 75, 100, 125, 150, 200];
    let currentZoomPercent = $state(100);
    let zoomSelectValue = $state('100');

    // 监听 viewport 变化同步缩放显示值
    $effect(() => {
        const vp = store.getViewport();
        if (vp) {
            currentZoomPercent = Math.round(vp.zoom * 100);
            zoomSelectValue = String(currentZoomPercent);
        }
    });

    function handleZoomSelect(event: Event) {
        const value = parseInt((event.target as HTMLSelectElement).value, 10);
        if (!isNaN(value)) {
            zoomSelectValue = String(value);
            const zoom = value / 100;
            const viewport = store.getViewport() || { x: 0, y: 0, zoom: 1 };
            if (typeof (svelteFlow as any).setViewport === 'function') {
                (svelteFlow as any).setViewport(
                    {
                        x: viewport.x,
                        y: viewport.y,
                        zoom
                    },
                    { duration: 180 }
                );
            } else {
                (svelteFlow as any).zoomTo?.(zoom, { duration: 180 });
            }
        }
    }

    function fitViewCanvas() {
        svelteFlow.fitView({ duration: 220, padding: 0.2 });
    }

    function toggleCanvasLock() {
        canvasLocked = !canvasLocked;
    }

    const onDragOver = (event: DragEvent) => {
        event.preventDefault();
        if (event.dataTransfer) {
            event.dataTransfer.dropEffect = 'move';
        }
    };

    const onDrop = (event: DragEvent) => {
        event.preventDefault();

        const position = svelteFlow.screenToFlowPosition({
            x: event.clientX - 250,
            y: event.clientY - 100
        });

        const baseNodeJsonString = event.dataTransfer?.getData('application/tinyflow');
        if (!baseNodeJsonString) {
            return;
        }

        const baseNode = JSON.parse(baseNodeJsonString);
        const newNode = {
            id: `node_${genShortId()}`,
            position,
            data: {},
            ...baseNode
        } satisfies Node;

        store.addNode(newNode);
        store.selectNodeOnly(newNode.id);
    };


    const { getNode } = useGetNode();


    const isValidConnection = (conn: any) => {
        const sourceNode = getNode(conn.source)!;
        const targetNode = getNode(conn.target)!;
        const sourceParentId = getResolvedParentId(sourceNode);
        const targetParentId = getResolvedParentId(targetNode);

        // 阻止循环节点连接到父级节点 或者 父级节点连接到子级节点
        if (conn.sourceHandle === 'loop_handle' || sourceParentId) {
            const edges = svelteFlow.getEdges();
            for (let edge of edges) {
                if (edge.target === conn.target) {
                    const edgeSourceNode = getNode(edge.source) as Node;
                    const edgeSourceParentId = getResolvedParentId(edgeSourceNode);
                    if (conn.sourceHandle === 'loop_handle' && edgeSourceParentId !== sourceNode.id) {
                        return false;
                    }
                    if (sourceParentId && edgeSourceParentId !== sourceParentId) {
                        return false;
                    }
                }
            }
        }

        if (!sourceParentId && targetParentId && targetParentId !== sourceNode.id) {
            return false;
        }

        // 允许链接
        return true;
    };

    const { getNodesFromSource } = useGetNodesFromSource();
    const { getNodeRelativePosition } = useGetNodeRelativePosition();
    const { ensureParentInNodesBefore } = useEnsureParentInNodesBefore();

    function collectAffectedNodeIds(rootNodeIds: string[], edges: Edge[] = store.getEdges()) {
        const affectedNodeIds = new Set<string>();
        const visit = (nodeId: string) => {
            if (!nodeId || affectedNodeIds.has(nodeId)) {
                return;
            }
            affectedNodeIds.add(nodeId);
            edges
                .filter((edge) => edge.source === nodeId && edge.sourceHandle !== 'loop_handle')
                .forEach((edge) => {
                    if (edge.target) {
                        visit(edge.target);
                    }
                });
        };
        rootNodeIds.forEach(visit);
        return Array.from(affectedNodeIds);
    }

    function reconcileBindingsForNodes(
        nodeIds: string[],
        options?: {
            nodes?: Node[];
            edges?: Edge[];
        }
    ) {
        const uniqueNodeIds = Array.from(new Set(nodeIds.filter((nodeId) => asString(nodeId).trim())));
        if (uniqueNodeIds.length === 0) {
            return;
        }
        queueMicrotask(() => {
            const nodes = options?.nodes || store.getNodes();
            const edges = options?.edges || store.getEdges();
            const patches = buildSequentialFieldBindingPatches(uniqueNodeIds, nodes, edges);
            patches.forEach(({ nodeId, patch }) => {
                store.updateNodeData(nodeId, patch);
            });
        });
    }

    const onconnectend = (event: any, state: any) => {
        if (!state.isValid) {
            if (state.toNode) {
                return;
            }
            const fromNode = state.fromNode as Node | null;
            if (fromNode) {
                const fromHandle = state.fromHandle as { id?: string } | null;
                const point = getEventClientPosition(event);
                pendingConnection = {
                    source: fromNode.id,
                    sourceHandle: fromHandle?.id,
                    startClientX: connectStartPoint?.x ?? point.x,
                    startClientY: connectStartPoint?.y ?? point.y,
                    clientX: point.x,
                    clientY: point.y
                };
                nodePickerVisible = true;
            }
            return;
        }
        closeNodePicker();

        const toNode = state.toNode as Node;
        const targetParentId = getResolvedParentId(toNode);
        if (targetParentId) {
            return;
        }

        const fromNode = state.fromNode as Node;
        const fromHande = state.fromHandle as any;
        const sourceParentId = getResolvedParentId(fromNode);

        const newNode = {
            position: { ...toNode.position }
        } as Node;

        if (fromHande.id === 'loop_handle') {
            newNode.parentId = fromNode.id;
        } else if (sourceParentId) {
            newNode.parentId = sourceParentId;
        }

        if (newNode.parentId) {

            const { x, y } = getNodeRelativePosition(newNode.parentId);

            newNode.position = {
                x: toNode.position.x - x,
                y: toNode.position.y - y
            };

            svelteFlow.updateNode(toNode.id, newNode);

            // 更新目标节点的所有后续的链接节点
            const nodesFromToNode = getNodesFromSource(toNode.id);
            nodesFromToNode.forEach((node) => {
                svelteFlow.updateNode(node.id, {
                    parentId: newNode.parentId,
                    position: {
                        x: node.position.x - x,
                        y: node.position.y - y
                    }
                });
            });

            ensureParentInNodesBefore(newNode.parentId, toNode.id);
        }

        // 显示边面板
        setTimeout(() => {
            store.getEdges().forEach((edge) => {
                if (edge.target === toNode.id && edge.source == fromNode.id) {
                    showEdgePanel = true;
                    currentEdge = edge;
                }
            });
        });
    };


    const { getEdgesByTarget } = useGetEdgesByTarget();
    const onDelete = (params: any) => {
        const deleteEdges = params.edges as Edge[];
        const affectedRootNodeIds = new Set<string>();
        deleteEdges.forEach((edge) => {
            if (edge.target) {
                affectedRootNodeIds.add(edge.target);
            }
            if (edge.id === currentEdge?.id) {
                currentEdge = null;
                showEdgePanel = false;
            }
            const targetNode = getNode(edge.target) as Node;
            const targetParentId = getResolvedParentId(targetNode as Node);
            if (targetNode && targetParentId) {
                const nodeEdges = getEdgesByTarget(edge.target);
                // const loopNode = getNode(targetNode.parentId) as Node;
                const { x, y } = getNodeRelativePosition(targetParentId);
                if (nodeEdges.length === 0) {
                    svelteFlow.updateNode(targetNode.id, {
                        parentId: undefined,
                        position: {
                            x: targetNode.position.x + x,
                            y: targetNode.position.y + y
                        }
                    });

                    // 更新目标节点的所有后续的链接节点
                    const nodesFromSource = getNodesFromSource(targetNode.id);
                    nodesFromSource.forEach((node) => {
                        svelteFlow.updateNode(node.id, {
                            parentId: undefined,
                            position: {
                                x: node.position.x + x,
                                y: node.position.y + y
                            }
                        });
                    });
                } else {
                    let hasSameParent = false;
                    for (let i = 0; i < nodeEdges.length; i++) {
                        const edge = nodeEdges[i];
                        const sourceNode = getNode(edge.source) as Node;
                        if (getResolvedParentId(sourceNode) || sourceNode.type === 'loopNode') {
                            hasSameParent = true;
                            break;
                        }
                    }
                    if (!hasSameParent) {
                        svelteFlow.updateNode(targetNode.id, {
                            parentId: undefined,
                            position: {
                                x: targetNode.position.x + x,
                                y: targetNode.position.y + y
                            }
                        });

                        // 更新目标节点的所有后续的链接节点
                        const nodesFromSource = getNodesFromSource(targetNode.id);
                        nodesFromSource.forEach((node) => {
                            svelteFlow.updateNode(node.id, {
                                parentId: undefined,
                                position: {
                                    x: node.position.x + x,
                                    y: node.position.y + y
                                }
                            });
                        });
                    }
                }
            }
        });
        if (affectedRootNodeIds.size > 0) {
            queueMicrotask(() => {
                reconcileBindingsForNodes(collectAffectedNodeIds(Array.from(affectedRootNodeIds)));
            });
        }
    };

    const { deleteEdge } = useDeleteEdge();


    const onconnectstart = (event: any, node: any) => {
        const point = getEventClientPosition(event);
        connectStartPoint = { x: point.x, y: point.y };
    };


    const onconnect = (event: any) => {
        const targetNodeId = asString(event?.target).trim();
        if (!targetNodeId) {
            return;
        }
        const sourceNodeId = asString(event?.source).trim();
        const projectedEdges = [...store.getEdges()];
        const hasSameEdge = projectedEdges.some((edge) =>
            edge.source === sourceNodeId
            && edge.target === targetNodeId
            && (edge.sourceHandle || '') === asString(event?.sourceHandle).trim()
            && (edge.targetHandle || '') === asString(event?.targetHandle).trim()
        );

        if (!hasSameEdge && sourceNodeId) {
            projectedEdges.push({
                id: asString(event?.id).trim() || `edge_${genShortId()}`,
                source: sourceNodeId,
                target: targetNodeId,
                sourceHandle: asString(event?.sourceHandle).trim() || undefined,
                targetHandle: asString(event?.targetHandle).trim() || undefined,
            } as Edge);
        }

        reconcileBindingsForNodes(
            collectAffectedNodeIds([targetNodeId], projectedEdges),
            {
                edges: projectedEdges
            }
        );
    };

    function applyAutoBindingsForNode(nodeId: string) {
        reconcileBindingsForNodes([nodeId]);
    }

    const { copyHandler, pasteHandler } = useCopyPasteHandler();

    const handleKeyDown = (e: KeyboardEvent) => {
        if (nodePickerVisible && e.key === 'Escape') {
            e.preventDefault();
            closeNodePicker();
            return;
        }

        if (isInEditableElement()) {
            return;
        }

        if ((e.ctrlKey || e.metaKey) && e.key === 'c') {
            e.preventDefault();
            copyHandler(e);
        }

        if ((e.ctrlKey || e.metaKey) && e.key === 'a') {
            e.preventDefault();
            // 全选所有节点
            store.updateNodes((nodes) => {
                return nodes.map(node => ({ ...node, selected: true }));
            });

            store.updateEdges((edges) => {
                return edges.map(edge => ({ ...edge, selected: true }));
            });
        }
    };

    const handleGlobalPaste = async (event: ClipboardEvent) => {
        // 只在“非输入态”下处理流程图粘贴
        if (isInEditableElement()) {
            return;
        }

        pasteHandler(event);
    };

    function handleGlobalPointerDown(event: PointerEvent) {
        if (readonly) {
            return;
        }
        if (!nodePickerVisible || !inlineNodePickerEl) {
            return;
        }
        const target = event.target as HTMLElement | null;
        if (target && inlineNodePickerEl.contains(target)) {
            return;
        }
        closeNodePicker();
    }

    onMount(() => {
        onInit(svelteFlow);
        store.updateEdges((edges) => edges.map((edge) => ensureEdgeVisualDefaults(edge)));
        repairOrphanParentNodes();
        if (!readonly) {
            window.addEventListener('keydown', handleKeyDown);
            window.addEventListener('paste', handleGlobalPaste);
            window.addEventListener('pointerdown', handleGlobalPointerDown);
        }
    });

    onDestroy(() => {
        if (!readonly) {
            window.removeEventListener('keydown', handleKeyDown);
            window.removeEventListener('paste', handleGlobalPaste);
            window.removeEventListener('pointerdown', handleGlobalPointerDown);
        }
    });

    const customNodeTypes = {
        // ...nodeTypes
    } as NodeTypes;
    const customEdgeTypes = {
        flow: FlowEdge
    } as EdgeTypes;

    const customNodes = options.customNodes;
    if (customNodes) {
        for (let key of Object.keys(customNodes)) {
            customNodeTypes[key] = CustomNode as any;
        }
    }

    const onDataChange = options.onDataChange;
    $effect(() => {
        onDataChange?.({
            nodes: store.getNodes(),
            edges: store.getEdges(),
            viewport: store.getViewport()
        });
    });


</script>


<div
    class:tf-canvas-locked={canvasLocked}
    style="position: relative; height: 100%; width: 100%;overflow: hidden"
    bind:this={flowRootEl}
>
    <SvelteFlow nodeTypes={{ ...nodeTypes, ...customNodeTypes}}
                edgeTypes={customEdgeTypes}
                bind:nodes={store.getNodes, store.setNodes}
                bind:edges={store.getEdges, store.setEdges}
                bind:viewport={store.getViewport, store.setViewport}
                nodesDraggable={nodesDraggable && !canvasLocked}
                nodesConnectable={nodesConnectable && !canvasLocked}
                elementsSelectable={elementsSelectable && !canvasLocked}
                panOnDrag={readonly ? true : !canvasLocked}
                zoomOnScroll={readonly ? true : !canvasLocked}
                zoomOnDoubleClick={readonly ? true : !canvasLocked}
                ondrop={dropEnabled ? onDrop : undefined}
                ondragover={dropEnabled ? onDragOver : undefined}
                isValidConnection={isValidConnection}
                onconnectend={connectionEnabled ? onconnectend : undefined}
                onconnectstart={connectionEnabled ? onconnectstart : undefined}
                onconnect={connectionEnabled ? onconnect : undefined}
                connectionRadius={50}
                connectionLineComponent={FlowConnectionLine}
                onedgeclick={(e) => {
                    if (readonly || hideEdgePanel) {
                        return;
                    }
                    showEdgePanel = true;
                    currentEdge = e.edge;
                }}
                onbeforeconnect={(edge: any) => normalizeEdgeBeforeConnect(edge)}
                ondelete={readonly ? undefined : onDelete}
                onclick={(e) => {
                    if (readonly || hideEdgePanel) {
                        return;
                    }
                    const el = e.target as HTMLElement;
                    if (el.classList.contains("svelte-flow__edge-interaction")
                        || el.classList.contains('panel-content')
                        || el.closest('.panel-content')){
                     return
                    }
                     showEdgePanel = false;
                     currentEdge = null;
                }}
                defaultEdgeOptions={{
                    type: 'flow',
                    markerEnd: {
                        type: MarkerType.ArrowClosed,
                        width: 20,
                        height: 20
                    }
                }}
    >
        <Background />
        {#if !hideMiniMap}
            <MiniMap />
        {/if}

        {#if showEdgePanel}
            <Panel>
                <div class="panel-content">
                    <div>边属性设置</div>
                    <div class="setting-title">边条件设置</div>
                    <div class="setting-item">
                        {#if currentEdge?.data?.managedByConditionNode}
                            <div class="readonly-edge-settings">
                                <div class="readonly-edge-branch">
                                    分支：{currentEdge?.data?.branchLabel || currentEdge?.data?.branchId || '-'}
                                </div>
                                <Textarea
                                    rows={3}
                                    style="width: 100%"
                                    disabled={true}
                                    value={asString(currentEdge?.data?.condition)}
                                />
                                <div class="readonly-edge-tip">
                                    该连线由条件节点托管，条件表达式不可在边面板手动修改。
                                </div>
                            </div>
                        {:else}
                            <Textarea
                                rows={3}
                                placeholder="请输入边条件"
                                style="width: 100%"
                                value={asString(currentEdge?.data?.condition)}
                                onchange={(e)=>{
                                    if (currentEdge){
                                        updateEdgeData(currentEdge.id, {
                                            condition: (e.target as HTMLTextAreaElement)?.value || ''
                                        })
                                    }
                                }}
                            />
                        {/if}
                    </div>
                    <div class="setting-item" style="padding: 8px 0">
                        <Button
                            onclick={() => {
                                if (currentEdge?.id) {
                                    deleteEdge(currentEdge.id)
                                }
                                showEdgePanel = false;
                            }}
                        >
                            删除
                        </Button>

                        <Button
                            primary={true}
                            onclick={() => {
                                showEdgePanel = false;
                            }}
                        >
                            保存
                        </Button>
                    </div>
                </div>
            </Panel>
        {/if}
    </SvelteFlow>
    {#if nodePickerVisible && !hideNodePicker}
        {#if pendingConnectionLine}
            <svg class="node-picker-connection-line" width="100%" height="100%">
                <FlowMarkerDefs id="tf-flow-inline-arrow-closed" />
                <FlowLinePath
                    path={pendingConnectionLine.path}
                    markerEnd="url(#tf-flow-inline-arrow-closed)"
                    animated={false}
                />
            </svg>
        {/if}
        <div
            class="node-picker-inline"
            style={inlineNodePickerStyle}
            bind:this={inlineNodePickerEl}
        >
            <NodePicker nodes={availableNodes} onSelect={handlePickerSelectNode} />
        </div>
    {/if}
    {#if !hideBottomDock}
    <div class="tf-bottom-dock">
        <div class="tf-unified-bar">
            <!-- 缩放百分比选择器 -->
            <select
                class="tf-zoom-select tf-zoom-select-simple"
                value={zoomSelectValue}
                onchange={handleZoomSelect}
                aria-label="缩放比例"
            >
                {#each zoomOptions as opt}
                    <option value={String(opt)}>{opt}%</option>
                {/each}
                {#if !zoomOptions.includes(currentZoomPercent)}
                    <option value={String(currentZoomPercent)} selected>{currentZoomPercent}%</option>
                {/if}
            </select>

            <!-- 适配视图按钮 -->
            <button
                class="tf-bar-btn"
                onclick={fitViewCanvas}
                aria-label="适配视图"
                title="适配视图"
            >
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                    <path d="M7 7H10V5H5V10H7V7ZM14 5V7H17V10H19V5H14ZM17 17H14V19H19V14H17V17ZM7 14H5V19H10V17H7V14Z"></path>
                </svg>
            </button>

            <!-- 锁定/解锁按钮 -->
            <button
                class="tf-bar-btn {canvasLocked ? 'tf-bar-btn-active' : ''}"
                onclick={toggleCanvasLock}
                aria-label={canvasLocked ? '解锁画布' : '锁定画布'}
                title={canvasLocked ? '解锁画布' : '锁定画布'}
            >
                {#if canvasLocked}
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M6 10V8C6 4.68629 8.68629 2 12 2C15.3137 2 18 4.68629 18 8V10H19C19.5523 10 20 10.4477 20 11V21C20 21.5523 19.5523 22 19 22H5C4.44772 22 4 21.5523 4 21V11C4 10.4477 4.44772 10 5 10H6ZM8 10H16V8C16 5.79086 14.2091 4 12 4C9.79086 4 8 5.79086 8 8V10Z"></path>
                    </svg>
                {:else}
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                        <path d="M7 10V8C7 5.23858 9.23858 3 12 3C14.7614 3 17 5.23858 17 8V10H20V22H4V10H7ZM9 10H15V8C15 6.34315 13.6569 5 12 5C10.3431 5 9 6.34315 9 8V10Z"></path>
                    </svg>
                {/if}
            </button>

            <!-- 分割线 -->
            <div class="tf-bar-divider"></div>

            <!-- 增加节点按钮（由 Toolbar 渲染） -->
            <Toolbar nodes={availableNodes} onSelectNode={handleToolbarSelectNode} />

        </div>
        {#if onRunTest}
            <button
                class="tf-bar-run-btn"
                onclick={() => onRunTest?.()}
                aria-label="试运行"
                title="试运行"
            >
                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                    <path d="M8 5V19L19 12L8 5Z"></path>
                </svg>
                试运行
            </button>
        {/if}
    </div>
    {/if}
</div>

<style>
    .panel-content {
        padding: 10px;
        background-color: var(--tf-bg-surface);
        border-radius: 5px;
        box-shadow: var(--tf-shadow-soft);
        width: 200px;
        border: 1px solid var(--tf-border-color);
        color: var(--tf-text-primary);
    }

    .setting-title {
        margin: 10px 0;
        font-size: 12px;
        color: var(--tf-text-muted);
    }

    .setting-item {
        display: flex;
        gap: 5px;
        align-items: center;
        justify-content: end;
    }

    .readonly-edge-settings {
        width: 100%;
    }

    .readonly-edge-branch {
        font-size: 12px;
        color: var(--tf-text-secondary);
        margin-bottom: 8px;
    }

    .readonly-edge-tip {
        margin-top: 6px;
        font-size: 12px;
        color: var(--tf-text-muted);
    }

    .node-picker-inline {
        position: absolute;
        z-index: 270;
        min-width: 300px;
        border: 1px solid var(--tf-border-color);
        border-radius: 12px;
        background: var(--tf-bg-surface);
        box-shadow: 0 14px 32px rgba(15, 23, 42, 0.2);
        padding: 8px;
        overflow: hidden;
    }

    .node-picker-connection-line {
        position: absolute;
        inset: 0;
        z-index: 268;
        pointer-events: none;
    }

    :global(.tf-canvas-locked .svelte-flow__pane),
    :global(.tf-canvas-locked .svelte-flow__nodes),
    :global(.tf-canvas-locked .svelte-flow__edges),
    :global(.tf-canvas-locked .svelte-flow__connectionline),
    :global(.tf-canvas-locked .svelte-flow__selection) {
        pointer-events: none !important;
    }
</style>
