<script setup>
import {
  ref,
  onMounted,
  computed,
  shallowRef,
  watch,
  defineExpose,
  nextTick,
  markRaw,
} from "vue";
import { VueFlow, useVueFlow, Panel } from "@vue-flow/core";
import { Background } from "@vue-flow/background";
import { Controls } from "@vue-flow/controls";
import { MiniMap } from "@vue-flow/minimap";
import { nodeTypes as customNodeTypes, nodeStyles } from "~/composables/processFlowNodes";
import InteractiveArrowEdge from "./InteractiveArrowEdge.vue";
import "@vue-flow/core/dist/style.css";
import "@vue-flow/core/dist/theme-default.css";
import "@vue-flow/controls/dist/style.css";
import "@vue-flow/minimap/dist/style.css";

// Add Material Icons import
const materialIconsLink = document.createElement("link");
materialIconsLink.href =
  "https://fonts.googleapis.com/icon?family=Material+Icons";
materialIconsLink.rel = "stylesheet";
document.head.appendChild(materialIconsLink);

const props = defineProps({
  initialNodes: {
    type: Array,
    default: () => [],
  },
  initialEdges: {
    type: Array,
    default: () => [],
  },
  highlightedNodeId: {
    type: String,
    default: null,
  },
});

const emit = defineEmits([
  "nodeClick",
  "edgeClick",
  "paneClick",
  "nodesChange",
  "edgesChange",
  "nodeSelected",
  "edgeSelected",
]);

// Get the flow instance
const { flowInstance } = useVueFlow();

// Initialize Vue Flow
const {
  nodes,
  edges,
  addNodes,
  addEdges,
  onNodesChange,
  onEdgesChange,
  onConnect,
  fitView,
  project,
  removeNodes,
  removeEdges,
  updateNodeInternals,
  setViewport,
  getViewport,
} = useVueFlow({
  defaultEdgeOptions: {
    animated: true,
    type: "custom",
    style: {
      stroke: "#555",
      strokeWidth: 2,
    },
    markerEnd: "url(#arrow)",
  },
  deleteKeyCode: "Delete",
  selectionKeyCode: "Shift",
  multiSelectionKeyCode: "Control",
  connectionMode: "strict",
  edgeUpdaterRadius: 12,
  edgesUpdatable: true,
  defaultMarkerColor: "#555",
  isValidConnection: (connection) => {
    return true;
  },
});

// Define custom edge types - use markRaw to prevent reactivity issues
const edgeTypes = {
  custom: shallowRef(InteractiveArrowEdge),
};

// Default nodes if empty
const defaultNodes = [];

// Default edges if empty
const defaultEdges = [];

// Flow configuration
const flowOptions = ref({
  defaultZoom: 1,
  minZoom: 0.2,
  maxZoom: 4,
  fitViewOnInit: true,
  snapToGrid: true,
  snapGrid: [15, 15],
  edgeUpdaterRadius: 10,
  connectionMode: "strict",
  connectionRadius: 25,
  elevateEdgesOnSelect: true,
  nodesDraggable: true,
  nodesConnectable: true,
  elementsSelectable: true,
  selectNodesOnDrag: false,
  panOnDrag: [0, 2],
  panOnScroll: false,
  zoomOnScroll: true,
  zoomOnPinch: true,
  zoomOnDoubleClick: false,
  connectOnClick: false,
});

// Use shallowRef for selected node to avoid unnecessary reactivity
const selectedNode = shallowRef(null);

// State management for preventing recursive updates
const isUpdatingNodes = ref(false);
const isUpdatingEdges = ref(false);

// Handle node selection
const onNodeClick = ({ node }) => {
  // Check if node exists and has required properties
  if (!node || !node.id) {
    console.warn("Invalid node clicked - Missing required properties");
    return;
  }

  try {
    // Create a plain object copy of the node to avoid reactivity issues
    const nodeData = {
      id: node.id,
      type: node.type,
      label: node.label || (node.data && node.data.label) || "",
      data: node.data ? JSON.parse(JSON.stringify(node.data)) : {},
      position: node.dimensions
        ? {
            x: node.dimensions.x || 0,
            y: node.dimensions.y || 0,
          }
        : { x: 0, y: 0 },
    };

    // console.log('Processed node data:', nodeData);
    selectedNode.value = nodeData;
    emit("nodeSelected", nodeData);
  } catch (error) {
    console.error("Error processing node data:", error);
  }
};

// Handle edge click
const onEdgeClick = (event, edge) => {
  // Handle different parameter formats Vue Flow might send
  let actualEdge = edge;

  // Sometimes Vue Flow passes the edge in the event object
  if (!actualEdge && event && typeof event === "object") {
    if (event.edge) {
      actualEdge = event.edge;
    } else if (event.id) {
      // The event itself might be the edge
      actualEdge = event;
    }
  }

  // Add comprehensive null check for edge
  if (!actualEdge || !actualEdge.id) {
    console.warn("Invalid edge data received in onEdgeClick:", {
      event,
      edge,
      actualEdge,
    });
    return;
  }

  // Create a simplified copy of the edge data
  const edgeData = {
    id: actualEdge.id,
    source: actualEdge.source,
    target: actualEdge.target,
    label: actualEdge.label || "",
    sourceNode: nodes.value.find((node) => node.id === actualEdge.source),
    targetNode: nodes.value.find((node) => node.id === actualEdge.target),
  };

  emit("edgeSelected", edgeData);
  emit("edgeClick", actualEdge);
};

// Handle pane (background) click
const onPaneClick = () => {
  selectedNode.value = null;
  emit("paneClick");
};

// Window resize handler
const resizeFlow = () => {
  setTimeout(() => {
    fitView();
  }, 200);
};

onMounted(() => {
  // Initialize with provided nodes or default ones
  if (props.initialNodes.length) {
    console.log("Mounted: initialNodes", props.initialNodes);
    addNodes(props.initialNodes);
  } else {
    addNodes(defaultNodes);
  }

  // Initialize with provided edges or default ones
  if (props.initialEdges.length) {
    addEdges(props.initialEdges);
  } else {
    addEdges(defaultEdges);
  }

  // Setup window resize handler
  window.addEventListener("resize", resizeFlow);

  // Initial fit view
  setTimeout(() => {
    fitView();
  }, 100);
});

// Center on a specific node
const centerOnNode = (nodeId, nodePosition) => {
  if (!nodePosition) {
    // Try to find the node position from current nodes
    const targetNode = nodes.value.find((n) => n.id === nodeId);
    if (targetNode) {
      nodePosition = targetNode.position;
    } else {
      console.warn(
        "Could not find node to highlight:",
        nodeId,
        "Available nodes:",
        nodes.value.map((n) => n.id)
      );
      return;
    }
  }

  try {
    // Use the already available setViewport from useVueFlow
    // Calculate the center position
    // Account for the container size and panel offsets
    const containerRect = document
      .querySelector(".vue-flow")
      ?.getBoundingClientRect();
    const centerX = containerRect ? containerRect.width / 2 : 400;
    const centerY = containerRect ? containerRect.height / 2 : 300;

    // Calculate new viewport to center the node
    const newViewport = {
      x: centerX - nodePosition.x - 100, // Offset for left panel
      y: centerY - nodePosition.y,
      zoom: 1.2,
    };

    setViewport(newViewport, { duration: 800 });
  } catch (error) {
    console.error("Error in centerOnNode:", error);
  }
};

// The centerOnNode method will be exposed along with other methods below

// Watch for changes to highlightedNodeId and apply highlighting
watch(
  () => props.highlightedNodeId,
  (newHighlightedId, oldHighlightedId) => {
    // Remove highlight from previously highlighted node
    if (oldHighlightedId) {
      const oldNode = nodes.value.find((node) => node.id === oldHighlightedId);
      if (oldNode) {
        // Use node data approach for highlighting
        if (oldNode.data) {
          oldNode.data.highlighted = false;
        }
        // Also try class approach
        if (oldNode.class) {
          oldNode.class = oldNode.class.replace(" highlighted-node", "").trim();
        }

        // Try to find and modify the actual DOM element
        nextTick(() => {
          const nodeElement = document.querySelector(
            `[data-id="${oldHighlightedId}"]`
          );
          if (nodeElement) {
            nodeElement.classList.remove("highlighted-node");
            nodeElement.style.removeProperty("box-shadow");
            nodeElement.style.removeProperty("z-index");
            nodeElement.style.removeProperty("transition");

            const customNode = nodeElement.querySelector(".custom-node");
            if (customNode) {
              customNode.classList.remove("highlighted-node");
              customNode.style.removeProperty("box-shadow");
              customNode.style.removeProperty("z-index");
              customNode.style.removeProperty("transition");
            }
          }
        });
      }
    }

    // Add highlight to new highlighted node
    if (newHighlightedId) {
      const newNode = nodes.value.find((node) => node.id === newHighlightedId);
      if (newNode) {
        // Use node data approach for highlighting
        if (!newNode.data) {
          newNode.data = {};
        }
        newNode.data.highlighted = true;

        // Also try class approach
        if (!newNode.class?.includes("highlighted-node")) {
          newNode.class = (newNode.class || "").trim() + " highlighted-node";
        }

        // Try to find and modify the actual DOM element directly with inline styles
        nextTick(() => {
          const nodeElement = document.querySelector(
            `[data-id="${newHighlightedId}"]`
          );
          if (nodeElement) {
            nodeElement.classList.add("highlighted-node");
            nodeElement.setAttribute("data-highlighted", "true");

            // Apply subtle inline styles for immediate visual effect
            nodeElement.style.boxShadow =
              "0 0 0 2px #3b82f6, 0 4px 12px rgba(59, 130, 246, 0.3)";
            nodeElement.style.zIndex = "999";
            nodeElement.style.transition = "all 0.3s ease-in-out";

            const customNode = nodeElement.querySelector(".custom-node");
            if (customNode) {
              customNode.classList.add("highlighted-node");
              customNode.setAttribute("data-highlighted", "true");

              // Apply subtle styles to custom node as well
              customNode.style.boxShadow =
                "0 0 0 2px #3b82f6, 0 4px 12px rgba(59, 130, 246, 0.3)";
              customNode.style.zIndex = "999";
              customNode.style.transition = "all 0.3s ease-in-out";
            }
          } else {
            console.warn(
              "Could not find DOM element for node:",
              newHighlightedId
            );
          }
        });

        // Force node update
        updateNodeInternals([newHighlightedId]);
      } else {
        console.warn(
          "Could not find node to highlight:",
          newHighlightedId,
          "Available nodes:",
          nodes.value.map((n) => ({ id: n.id, type: n.type }))
        );
      }
    }
  }
);

// Watch for changes to initialNodes prop and update the canvas
watch(
  () => props.initialNodes,
  async (newNodes, oldNodes) => {
    console.log("Watcher: initialNodes changed", newNodes);

    if (newNodes && Array.isArray(newNodes) && !isUpdatingNodes.value) {
      isUpdatingNodes.value = true;

      try {
        // Instead of clearing all nodes, sync them intelligently
        const currentNodeIds = new Set(nodes.value.map((n) => n.id));
        const newNodeIds = new Set(newNodes.map((n) => n.id));

        // Remove nodes that are no longer in the new list
        const nodesToRemove = nodes.value.filter(
          (node) => !newNodeIds.has(node.id)
        );
        if (nodesToRemove.length > 0) {
          removeNodes(nodesToRemove);
        }

        // Add new nodes that aren't already present
        const nodesToAdd = newNodes.filter(
          (node) => !currentNodeIds.has(node.id)
        );
        if (nodesToAdd.length > 0) {
          addNodes([...nodesToAdd]); // Create a copy to avoid reactivity issues
        }

        // Update existing nodes that have changed
        newNodes.forEach((newNode) => {
          const existingNode = nodes.value.find((n) => n.id === newNode.id);
          if (existingNode) {
            // Always update the existing node to ensure latest data is reflected
            // This is critical for property changes to be visible in the canvas
            Object.assign(existingNode, {
              label: newNode.label,
              data: { ...newNode.data },
              position: { ...newNode.position },
            });

            // Always call updateNodeInternals to force re-render
            // This ensures property changes are reflected immediately
            updateNodeInternals([newNode.id]);
          }
        });

        // Fit view only if we added new nodes and this is significant change
        if (nodesToAdd.length > 0) {
          await nextTick();
          setTimeout(() => {
            fitView();
          }, 100);
        }
      } finally {
        // Use a small delay to prevent immediate re-triggering
        setTimeout(() => {
          isUpdatingNodes.value = false;
        }, 50);
      }
    }
  },
  { deep: true }
);

// Watch for changes to initialEdges prop and update the canvas
// This watcher depends on nodes being already present
watch(
  () => [props.initialEdges, nodes.value.length],
  async ([newEdges, nodeCount]) => {
    if (
      newEdges &&
      Array.isArray(newEdges) &&
      nodeCount > 0 &&
      !isUpdatingEdges.value
    ) {
      isUpdatingEdges.value = true;

      try {
        // Instead of clearing all edges, sync them intelligently
        const currentEdgeIds = new Set(edges.value.map((e) => e.id));
        const newEdgeIds = new Set(newEdges.map((e) => e.id));

        // CRITICAL: Be more conservative about edge removal
        // Only remove edges that are definitely not in the new list AND whose nodes don't exist
        const edgesToRemove = edges.value.filter((edge) => {
          const isInNewList = newEdgeIds.has(edge.id);

          if (isInNewList) {
            return false; // Don't remove if it's in the new list
          }

          // Double-check that both source and target nodes still exist
          const sourceExists = nodes.value.some(
            (node) => node.id === edge.source
          );
          const targetExists = nodes.value.some(
            (node) => node.id === edge.target
          );

          // Only remove if nodes don't exist (orphaned edges)
          const shouldRemove = !sourceExists || !targetExists;

          if (shouldRemove) {
            console.log(
              `🗑️ Removing orphaned edge ${edge.id}: source ${edge.source} exists: ${sourceExists}, target ${edge.target} exists: ${targetExists}`
            );
          }

          return shouldRemove;
        });

        if (edgesToRemove.length > 0) {
          console.log(
            "🗑️ Removing edges:",
            edgesToRemove.map((e) => `${e.source}->${e.target} (${e.id})`)
          );
          removeEdges(edgesToRemove);
        }

        if (newEdges.length > 0) {
          // Verify all nodes exist before adding edges
          const validEdges = newEdges.filter((edge) => {
            const sourceExists = nodes.value.some(
              (node) => node.id === edge.source
            );
            const targetExists = nodes.value.some(
              (node) => node.id === edge.target
            );

            if (!sourceExists || !targetExists) {
              console.warn(
                `⚠️ Skipping edge ${edge.id}: source ${edge.source} exists: ${sourceExists}, target ${edge.target} exists: ${targetExists}`
              );
              return false;
            }

            return true;
          });

          // Add new edges that aren't already present
          const edgesToAdd = validEdges.filter(
            (edge) => !currentEdgeIds.has(edge.id)
          );

          if (edgesToAdd.length > 0) {
            console.log(
              "➕ Adding new edges:",
              edgesToAdd.map((e) => `${e.source}->${e.target} (${e.id})`)
            );

            // Ensure all edges have proper handle specifications
            const edgesWithHandles = edgesToAdd.map((edge) => {
              // IMPORTANT: If edge already has sourceHandle and targetHandle, preserve them exactly as they are
              if (edge.sourceHandle && edge.targetHandle) {
                console.log(
                  `🔗 Edge ${edge.id} already has handles: ${edge.sourceHandle} -> ${edge.targetHandle}`
                );
                return edge;
              }

              // Otherwise, generate default handles based on node types
              const sourceNode = nodes.value.find((n) => n.id === edge.source);
              const targetNode = nodes.value.find((n) => n.id === edge.target);

              let sourceHandle = edge.sourceHandle;
              let targetHandle = edge.targetHandle;

              // Generate default source handle if missing
              if (!sourceHandle && sourceNode) {
                if (sourceNode.type === "start") {
                  sourceHandle = `${edge.source}-bottom`; // Start nodes prefer bottom output
                } else if (sourceNode.type === "gateway") {
                  sourceHandle = `${edge.source}-right`; // Gateway nodes prefer right output for first connection
                } else {
                  sourceHandle = `${edge.source}-bottom`; // Most nodes prefer bottom output
                }
              }

              // Generate default target handle if missing
              if (!targetHandle && targetNode) {
                if (targetNode.type === "end") {
                  targetHandle = `${edge.target}-top`; // End nodes prefer top input
                } else {
                  targetHandle = `${edge.target}-top`; // Most nodes prefer top input
                }
              }

              console.log(
                `🔗 Generated handles for edge ${edge.id}: ${sourceHandle} -> ${targetHandle}`
              );
              return {
                ...edge,
                sourceHandle,
                targetHandle,
              };
            });

            addEdges([...edgesWithHandles]); // Create a copy to avoid reactivity issues
          }

          // Update existing edges that have changed - IMPORTANT: preserve handle positions
          let updatedEdgeCount = 0;
          newEdges.forEach((newEdge) => {
            const existingEdge = edges.value.find((e) => e.id === newEdge.id);
            if (existingEdge) {
              // Check if the edge has actually changed before updating
              const hasChanges =
                existingEdge.label !== newEdge.label ||
                JSON.stringify(existingEdge.style) !==
                  JSON.stringify(newEdge.style);

              if (hasChanges) {
                Object.assign(existingEdge, {
                  label: newEdge.label,
                  // CRITICAL: Preserve existing handles if they exist
                  sourceHandle:
                    existingEdge.sourceHandle || newEdge.sourceHandle,
                  targetHandle:
                    existingEdge.targetHandle || newEdge.targetHandle,
                  style: newEdge.style ? { ...newEdge.style } : undefined,
                });
                updatedEdgeCount++;
              }
            }
          });

          if (updatedEdgeCount > 0) {
            console.log("🔄 Updated existing edges:", updatedEdgeCount);
          }
        }
      } finally {
        // Use a small delay to prevent immediate re-triggering
        setTimeout(() => {
          isUpdatingEdges.value = false;
        }, 50);
      }
    } else if (
      newEdges &&
      Array.isArray(newEdges) &&
      newEdges.length > 0 &&
      nodeCount === 0
    ) {
      console.log(
        "⚠️ ProcessFlowCanvas: Edges provided but no nodes yet, waiting..."
      );
    }
  },
  { deep: true }
);

// Handle node changes
onNodesChange((changes) => {
  emit("nodesChange", changes, nodes.value);
});

// Handle edge changes
onEdgesChange((changes) => {
  emit("edgesChange", changes, edges.value);
});

// Handle new connections
const handleConnect = (connection) => {
  if (!connection.source || !connection.target) return;

  // Try to determine if this is coming from a gateway
  const sourceNode = nodes.value.find((node) => node.id === connection.source);
  let label = "";

  // If the source is a gateway, we should add a label based on conditions
  if (sourceNode && sourceNode.type === "gateway") {
    // Check if there's a default path label
    if (sourceNode.data && sourceNode.data.defaultPath) {
      label = sourceNode.data.defaultPath;
    }

    // For existing gateway connections, check if we should use a condition's output
    const existingEdges = edges.value.filter(
      (edge) => edge.source === connection.source
    );
    if (
      existingEdges.length === 0 &&
      sourceNode.data.conditions &&
      sourceNode.data.conditions.length > 0
    ) {
      // If this is the first connection and we have conditions, use the first condition's output
      const firstCondition = sourceNode.data.conditions[0];
      if (firstCondition && firstCondition.output) {
        label = firstCondition.output;
      }
    } else if (sourceNode.data.conditions) {
      // If we already have connections, try to find an unused condition
      const usedOutputs = existingEdges.map((edge) => edge.label);
      const unusedCondition = sourceNode.data.conditions.find(
        (condition) =>
          condition.output && !usedOutputs.includes(condition.output)
      );

      if (unusedCondition) {
        label = unusedCondition.output;
      }
    }
  }

  const newEdge = {
    id: `${connection.source}-${connection.target}-${Date.now()}`,
    source: connection.source,
    target: connection.target,
    sourceHandle: connection.sourceHandle,
    targetHandle: connection.targetHandle,
    type: "custom",
    animated: true,
    style: {
      stroke: "#555",
      strokeWidth: 2,
    },
    markerEnd: "url(#arrow)",
    label: label,
  };

  // Add the edge directly to Vue Flow for immediate visual feedback
  addEdges([newEdge]);

  // Emit the edge change in the format the parent expects
  emit("edgesChange", [{ type: "add", id: newEdge.id }], edges.value);
};

// Handle node removal
const onNodeDelete = (event) => {
  // Check if we have a node in the event
  if (event && event.node) {
    removeNodes([event.node]);
    emit("nodesChange", nodes.value);
  }
};

// Handle edge removal
const onEdgeDelete = (event) => {
  // Check if we have an edge in the event
  if (event && event.edge) {
    removeEdges([event.edge]);
    emit("edgesChange", edges.value);
  }
};

// Handle delete key press to remove selected elements
const onDeleteKeyPress = () => {
  const { getSelectedNodes, getSelectedEdges } = flowInstance.value;
  const selectedNodes = getSelectedNodes();
  const selectedEdges = getSelectedEdges();

  if (selectedNodes.length > 0) {
    removeNodes(selectedNodes);
    emit("nodesChange", nodes.value);
  }

  if (selectedEdges.length > 0) {
    removeEdges(selectedEdges);
    emit("edgesChange", edges.value);
  }
};

// Handle drop event
const onDrop = (event) => {
  event.preventDefault();
  event.stopPropagation();

  try {
    // Get the dragged component data
    const componentData = JSON.parse(event.dataTransfer.getData("text/plain"));
    if (!componentData) return;

    // Get the Vue Flow wrapper element
    const flowWrapper = event.currentTarget;
    const bounds = flowWrapper.getBoundingClientRect();

    // Calculate the position relative to the wrapper
    const position = project({
      x: event.clientX - bounds.left,
      y: event.clientY - bounds.top,
    });

    // Create new node (both process nodes and shapes are treated as nodes in Vue Flow)
    const newNode = {
      id: `${componentData.type}-${Date.now()}`,
      type: componentData.type,
      position,
      label: componentData.label,
      data: {
        ...componentData.data,
        label: componentData.label,
      },
    };

    // For shape nodes, ensure they don't have connection handles
    if (componentData.data?.isShape) {
      // Shapes should not be connectable
      newNode.connectable = false;
    }

    // Add to Vue Flow for immediate visual feedback
    addNodes([newNode]);

    // IMPORTANT: Also emit the node to be added to the process store
    // This ensures the node persists in the application state and can be saved
    emit(
      "nodesChange",
      [{ type: "add", id: newNode.id, item: newNode }],
      nodes.value
    );
  } catch (error) {
    console.error("Error handling drop:", error);
  }
};

// Handle drag over
const onDragOver = (event) => {
  event.preventDefault();
  event.stopPropagation();
  event.dataTransfer.dropEffect = "copy";
};

// Define methods to expose to parent components
defineExpose({
  updateNode,
  addNode,
  removeNode,
  fitView,
  syncCanvas,
  centerOnNode,
  // Add Vue Flow save/restore methods
  toObject,
  fromObject,
  // Add direct access to Vue Flow methods for production fallbacks
  setNodes: (newNodes) => {
    try {
      if (Array.isArray(newNodes)) {
        nodes.value = newNodes;
      }
    } catch (error) {
      console.error('Error in setNodes:', error);
    }
  },
  setEdges: (newEdges) => {
    try {
      if (Array.isArray(newEdges)) {
        edges.value = newEdges;
      }
    } catch (error) {
      console.error('Error in setEdges:', error);
    }
  },
  // Provide access to the flow instance
  get flowInstance() {
    return flowInstance;
  }
});

// Update an existing node
function updateNode(nodeId, newData) {
  const nodeToUpdate = nodes.value.find((node) => node.id === nodeId);
  if (!nodeToUpdate) {
    console.warn(`Canvas updateNode: Node ${nodeId} not found`);
    return;
  }

  // Update the node properties - handle all provided data
  if (newData.label !== undefined) {
    nodeToUpdate.label = newData.label;
  }

  // Update the node data - merge with existing data
  if (newData.data !== undefined) {
    nodeToUpdate.data = {
      ...nodeToUpdate.data,
      ...newData.data,
    };
  }

  // Update position if provided
  if (newData.position !== undefined) {
    nodeToUpdate.position = { ...newData.position };
  }

  // Update style if provided
  if (newData.style !== undefined) {
    nodeToUpdate.style = {
      ...(nodeToUpdate.style || {}),
      ...newData.style,
    };

    // Also apply style directly to DOM element for immediate visual feedback
    nextTick(() => {
      const nodeElement = document.querySelector(`[data-id="${nodeId}"]`);
      if (nodeElement) {
        // Apply styles directly to the DOM element
        Object.entries(newData.style).forEach(([key, value]) => {
          nodeElement.style[key] = value;
        });
      }
    });
  }

  // Force re-render by updating node internals
  updateNodeInternals([nodeId]);

  return nodeToUpdate;
}

// Add a new node to the canvas
function addNode(node) {
  addNodes([node]);
  return node;
}

// Remove a node from the canvas
function removeNode(nodeId) {
  const nodeToRemove = nodes.value.find((node) => node.id === nodeId);
  if (!nodeToRemove) return;

  removeNodes([nodeToRemove]);
  return nodeToRemove;
}

// Manual sync function for explicit canvas updates
function syncCanvas(newNodes, newEdges) {
  console.log(
    "🔄 Manual canvas sync requested - nodes:",
    newNodes?.length || 0,
    "edges:",
    newEdges?.length || 0
  );

  // Use a small delay to ensure any pending Vue Flow operations complete
  setTimeout(async () => {
    try {
      // Sync nodes first
      if (newNodes && Array.isArray(newNodes)) {
        const currentNodeIds = new Set(nodes.value.map((n) => n.id));
        const newNodeIds = new Set(newNodes.map((n) => n.id));

        console.log(
          "📊 Current canvas nodes:",
          currentNodeIds.size,
          "New nodes:",
          newNodeIds.size
        );

        // Remove nodes that are no longer in the new list
        const nodesToRemove = nodes.value.filter(
          (node) => !newNodeIds.has(node.id)
        );
        if (nodesToRemove.length > 0) {
          console.log(
            "🗑️ Removing nodes:",
            nodesToRemove.map((n) => n.id)
          );
          removeNodes(nodesToRemove);
        }

        // Add new nodes that aren't already present
        const nodesToAdd = newNodes.filter(
          (node) => !currentNodeIds.has(node.id)
        );
        if (nodesToAdd.length > 0) {
          console.log(
            "➕ Adding new nodes:",
            nodesToAdd.map((n) => n.id)
          );
          addNodes([...nodesToAdd]);
        }

        // Update existing nodes - force update all properties
        newNodes.forEach((newNode) => {
          const existingNode = nodes.value.find((n) => n.id === newNode.id);
          if (existingNode) {
            // Always update to ensure property changes are reflected
            Object.assign(existingNode, {
              label: newNode.label,
              data: { ...newNode.data },
              position: { ...newNode.position },
            });
            // Always force re-render for property changes
            updateNodeInternals([newNode.id]);
          }
        });

        // Wait for nodes to be fully processed before handling edges
        await nextTick();
        await new Promise((resolve) => setTimeout(resolve, 100));
      }

      console.log(
        "📊 Canvas state after node sync - nodes:",
        nodes.value.length,
        "edges:",
        edges.value.length
      );

      // Sync edges after nodes are updated - CRITICAL: Only if we have nodes
      if (newEdges && Array.isArray(newEdges) && nodes.value.length > 0) {
        const currentEdgeIds = new Set(edges.value.map((e) => e.id));
        const newEdgeIds = new Set(newEdges.map((e) => e.id));

        console.log(
          "📊 Current canvas edges:",
          currentEdgeIds.size,
          "New edges:",
          newEdgeIds.size
        );

        // CRITICAL: Only remove edges that are definitely not in the new list
        // Be more conservative about edge removal to prevent accidental deletions
        const edgesToRemove = edges.value.filter((edge) => {
          const shouldRemove = !newEdgeIds.has(edge.id);
          if (shouldRemove) {
            // Double-check that both source and target nodes still exist
            const sourceExists = nodes.value.some(
              (node) => node.id === edge.source
            );
            const targetExists = nodes.value.some(
              (node) => node.id === edge.target
            );

            // Only remove if the edge is truly not needed OR if nodes don't exist
            return !sourceExists || !targetExists;
          }
          return false;
        });

        if (edgesToRemove.length > 0) {
          console.log(
            "🗑️ Removing edges:",
            edgesToRemove.map((e) => `${e.source}->${e.target} (${e.id})`)
          );
          removeEdges(edgesToRemove);
        }

        // Add new edges that aren't already present
        const edgesToAdd = newEdges.filter(
          (edge) => !currentEdgeIds.has(edge.id)
        );
        if (edgesToAdd.length > 0) {
          console.log(
            "➕ Adding new edges:",
            edgesToAdd.map((e) => `${e.source}->${e.target} (${e.id})`)
          );

          // Verify nodes exist and add handles
          const validEdges = edgesToAdd.filter((edge) => {
            const sourceExists = nodes.value.some(
              (node) => node.id === edge.source
            );
            const targetExists = nodes.value.some(
              (node) => node.id === edge.target
            );

            if (!sourceExists || !targetExists) {
              console.warn(
                `⚠️ Skipping edge ${edge.id}: source ${edge.source} exists: ${sourceExists}, target ${edge.target} exists: ${targetExists}`
              );
              return false;
            }

            return true;
          });

          console.log("✅ Valid edges to add:", validEdges.length);

          const edgesWithHandles = validEdges.map((edge) => {
            // If edge already has sourceHandle and targetHandle, use them
            if (edge.sourceHandle && edge.targetHandle) {
              console.log(
                `🔗 Edge ${edge.id} already has handles: ${edge.sourceHandle} -> ${edge.targetHandle}`
              );
              return edge;
            }

            const sourceNode = nodes.value.find((n) => n.id === edge.source);
            const targetNode = nodes.value.find((n) => n.id === edge.target);

            let sourceHandle = edge.sourceHandle;
            let targetHandle = edge.targetHandle;

            if (!sourceHandle && sourceNode) {
              if (sourceNode.type === "start") {
                sourceHandle = `${edge.source}-bottom`;
              } else if (sourceNode.type === "gateway") {
                sourceHandle = `${edge.source}-right`;
              } else {
                sourceHandle = `${edge.source}-bottom`;
              }
            }

            if (!targetHandle && targetNode) {
              if (targetNode.type === "end") {
                targetHandle = `${edge.target}-top`;
              } else {
                targetHandle = `${edge.target}-top`;
              }
            }

            console.log(
              `🔗 Generated handles for edge ${edge.id}: ${sourceHandle} -> ${targetHandle}`
            );
            return { ...edge, sourceHandle, targetHandle };
          });

          if (edgesWithHandles.length > 0) {
            console.log(
              "✅ Adding edges with handles:",
              edgesWithHandles.length
            );
            addEdges([...edgesWithHandles]);
          }
        }

        // Update existing edges - preserve handles and only update changed properties
        let updatedEdgeCount = 0;
        newEdges.forEach((newEdge) => {
          const existingEdge = edges.value.find((e) => e.id === newEdge.id);
          if (existingEdge) {
            // Check if update is needed
            const needsUpdate =
              existingEdge.label !== newEdge.label ||
              JSON.stringify(existingEdge.style) !==
                JSON.stringify(newEdge.style);

            if (needsUpdate) {
              Object.assign(existingEdge, {
                label: newEdge.label,
                // CRITICAL: Preserve existing handles if they exist
                sourceHandle: existingEdge.sourceHandle || newEdge.sourceHandle,
                targetHandle: existingEdge.targetHandle || newEdge.targetHandle,
                style: newEdge.style ? { ...newEdge.style } : undefined,
              });
              updatedEdgeCount++;
            }
          }
        });

        if (updatedEdgeCount > 0) {
          console.log("🔄 Updated existing edges:", updatedEdgeCount);
        }
      } else if (newEdges && Array.isArray(newEdges) && newEdges.length > 0) {
        console.warn(
          "⚠️ Cannot add edges: nodes not ready. Node count:",
          nodes.value.length
        );
      }

      console.log(
        "✅ Canvas sync completed - final state: nodes:",
        nodes.value.length,
        "edges:",
        edges.value.length
      );
    } catch (error) {
      console.error("❌ Error during canvas sync:", error);
    }
  }, 50); // Small delay to allow any pending operations to complete
}

// Add Vue Flow save method - captures complete flow state including viewport
function toObject() {
  try {
    // Create flow object using only essential data, excluding Vue Flow internals
    const flowObject = {
      nodes: nodes.value.map((node) => ({
        id: node.id,
        type: node.type,
        label: node.label,
        position: { ...node.position },
        data: { ...node.data },
        // Exclude: events, dragging, dimensions, handleBounds, computedPosition, etc.
      })),
      edges: edges.value.map((edge) => ({
        id: edge.id,
        source: edge.source,
        target: edge.target,
        sourceHandle: edge.sourceHandle,
        targetHandle: edge.targetHandle,
        label: edge.label || "",
        type: edge.type || "smoothstep",
        animated: edge.animated !== undefined ? edge.animated : true,
        data: edge.data ? { ...edge.data } : {},
        // Exclude: events, sourceNode, targetNode, sourceX, sourceY, targetX, targetY
      })),
      viewport: getViewport(),
    };

    return flowObject;
  } catch (error) {
    console.error("Error capturing flow state:", error);

    // Return a safe fallback object
    return {
      nodes: [],
      edges: [],
      viewport: { x: 0, y: 0, zoom: 1 },
    };
  }
}

// Add Vue Flow restore method - properly restores complete flow state
function fromObject(flowObject) {
  if (!flowObject) {
    console.warn("No flow object provided for restoration");
    return Promise.resolve();
  }

  if (!flowObject.nodes && !flowObject.edges) {
    console.warn("Flow object contains no nodes or edges");
    return Promise.resolve();
  }

  return new Promise((resolve) => {
    try {
      // Clear updating flags to ensure clean restoration
      isUpdatingNodes.value = false;
      isUpdatingEdges.value = false;

      // Wait for any pending operations to complete
      setTimeout(async () => {
        try {
          // 1. First, clear existing state
          if (nodes.value.length > 0) {
            removeNodes([...nodes.value]);
          }
          if (edges.value.length > 0) {
            removeEdges([...edges.value]);
          }

          // Wait for clearing to complete
          await nextTick();

          // 2. Restore nodes first
          if (
            flowObject.nodes &&
            Array.isArray(flowObject.nodes) &&
            flowObject.nodes.length > 0
          ) {
            const nodesToRestore = flowObject.nodes.map((node) => ({
              id: node.id,
              type: node.type,
              label: node.label || "",
              position: node.position || { x: 0, y: 0 },
              data: node.data || {},
              // Only include essential properties needed for Vue Flow
            }));

            addNodes(nodesToRestore);
            await nextTick();

            // Wait a bit more for nodes to be fully initialized
            await new Promise((resolve) => setTimeout(resolve, 100));
          }

          // 3. Restore edges after nodes are ready
          if (
            flowObject.edges &&
            Array.isArray(flowObject.edges) &&
            flowObject.edges.length > 0 &&
            nodes.value.length > 0
          ) {
            // Verify all edges have valid source and target nodes
            const validEdges = flowObject.edges.filter((edge) => {
              const sourceExists = nodes.value.some(
                (node) => node.id === edge.source
              );
              const targetExists = nodes.value.some(
                (node) => node.id === edge.target
              );

              if (!sourceExists || !targetExists) {
                console.warn(
                  `Skipping invalid edge ${edge.id}: source ${edge.source} exists: ${sourceExists}, target ${edge.target} exists: ${targetExists}`
                );
                return false;
              }

              return true;
            });

            if (validEdges.length > 0) {
              // Clean edge data before adding
              const cleanEdges = validEdges.map((edge) => ({
                id: edge.id,
                source: edge.source,
                target: edge.target,
                sourceHandle: edge.sourceHandle,
                targetHandle: edge.targetHandle,
                label: edge.label || "",
                type: edge.type || "custom",
                animated: edge.animated !== undefined ? edge.animated : true,
                data: edge.data || {},
                // Only include essential properties
              }));

              addEdges(cleanEdges);
              await nextTick();
            }
          }

          // 4. Finally, restore viewport position and zoom
          if (flowObject.viewport && typeof flowObject.viewport === "object") {
            const viewport = {
              x: flowObject.viewport.x || 0,
              y: flowObject.viewport.y || 0,
              zoom: flowObject.viewport.zoom || 1,
            };

            setViewport(viewport, { duration: 0 }); // No animation for restore
          }

          resolve();
        } catch (error) {
          console.error("Error during flow state restoration:", error);
          resolve(); // Resolve anyway to prevent hanging
        }
      }, 100); // Small delay to ensure clean state
    } catch (error) {
      console.error("Error in fromObject:", error);
      resolve(); // Resolve anyway to prevent hanging
    }
  });
}
</script>

<template>
  <div class="process-flow-container" @drop="onDrop" @dragover="onDragOver">
    <VueFlow
      v-if="nodes.length > 0"
      v-bind="flowOptions"
      class="bg-slate-50 process-flow"
      :nodeTypes="customNodeTypes"
      :edgeTypes="edgeTypes"
      @node-click="onNodeClick"
      @edge-click="onEdgeClick"
      @pane-click="onPaneClick"
      @connect="handleConnect"
      @nodeDoubleClick="onNodeDelete"
      @edgeDoubleClick="onEdgeDelete"
      @keyup.delete="onDeleteKeyPress"
    >
      <!-- Global SVG definitions for arrows -->
      <svg style="position: absolute; top: 0; left: 0; width: 0; height: 0">
        <defs>
          <marker
            id="arrow"
            viewBox="0 0 10 10"
            refX="9"
            refY="3"
            markerWidth="6"
            markerHeight="6"
            orient="auto"
            markerUnits="strokeWidth"
          >
            <path d="M0,0 L0,6 L9,3 z" fill="#555" />
          </marker>
          <marker
            id="arrow-selected"
            viewBox="0 0 10 10"
            refX="9"
            refY="3"
            markerWidth="6"
            markerHeight="6"
            orient="auto"
            markerUnits="strokeWidth"
          >
            <path d="M0,0 L0,6 L9,3 z" fill="#ff6b6b" />
          </marker>
          <marker
            id="arrow-animated"
            viewBox="0 0 10 10"
            refX="9"
            refY="3"
            markerWidth="6"
            markerHeight="6"
            orient="auto"
            markerUnits="strokeWidth"
          >
            <path d="M0,0 L0,6 L9,3 z" fill="#3b82f6" />
          </marker>
        </defs>
      </svg>
      <Background pattern-color="#aaa" gap="20" />
      <Controls />
      <MiniMap />
      <!-- Custom edge template -->
      <template #edge-custom="edgeProps">
        <InteractiveArrowEdge v-bind="edgeProps" />
      </template>
      <template #edge-label="{ label }">
        <div class="edge-label">{{ label }}</div>
      </template>
      <Panel position="top-right" class="node-controls">
        <div class="p-2 bg-white rounded shadow-sm text-sm">
          <div class="mb-1 font-medium">Controls:</div>
          <div>• Delete: Remove selected</div>
          <div>• Shift: Select nodes</div>
          <div>• Drag between nodes to connect</div>
          <div>• Double-click to remove</div>
          <div class="mt-2 mb-1 font-medium">Edge Features:</div>
          <div>• Arrows show flow direction</div>
          <div>• Select edge for controls</div>
          <div>• Blue dot = drag to reposition</div>
          <div>• Reset button restores path</div>
        </div>
      </Panel>
    </VueFlow>
  </div>
</template>

<style>
/* Node styles from ProcessFlowNodes.js are imported globally in a plugin */
.process-flow-container {
  width: 100%;
  min-height: 500px;
  border: 1px solid #e2e8f0;
  border-radius: 0;
  overflow: hidden;
  position: relative;
  flex: 1;
}

/* Make sure VueFlow takes full height */
:deep(.vue-flow) {
  height: 100% !important;
}

/* Ensure the flow wrapper fills container */
:deep(.vue-flow__container) {
  height: 100% !important;
}

/* Responsive adjustments */
@media (max-width: 768px) {
  .process-flow-container {
    height: calc(100vh - 150px);
    min-height: 400px;
  }
}

@media (max-width: 480px) {
  .process-flow-container {
    height: calc(100vh - 120px);
    min-height: 300px;
  }

  :deep(.vue-flow__controls) {
    transform: scale(0.8);
    transform-origin: bottom right;
  }

  :deep(.vue-flow__minimap) {
    transform: scale(0.8);
    transform-origin: top right;
  }
}

.edge-label {
  background-color: white;
  padding: 3px 8px;
  border-radius: 12px;
  font-size: 11px;
  font-weight: 600;
  border: 1.5px solid #3b82f6;
  color: #1e40af;
  box-shadow: 0 2px 8px rgba(59, 130, 246, 0.2);
  white-space: nowrap;
  z-index: 10;
}

/* Vue Flow custom styles */
:deep(.vue-flow__edge-path) {
  stroke: #555;
  stroke-width: 2px;
  transition:
    stroke-width 0.2s ease,
    stroke 0.2s ease;
}

:deep(.vue-flow__edge.selected .vue-flow__edge-path) {
  stroke: #ff6b6b;
  stroke-width: 3px;
  filter: drop-shadow(0 0 4px rgba(255, 107, 107, 0.3));
}

:deep(.vue-flow__edge.animated .vue-flow__edge-path) {
  stroke-dasharray: 5;
  animation: flowEdgeAnimation 2s linear infinite;
}

:deep(.vue-flow__edge:hover .vue-flow__edge-path) {
  stroke-width: 3px;
  filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.2));
}

/* Custom edge styles */
:deep(.custom-edge) {
  cursor: pointer;
}

:deep(.custom-edge.selected) {
  filter: drop-shadow(0 0 4px rgba(255, 107, 107, 0.5));
}

/* Edge updater styles */
:deep(.vue-flow__edge-updater) {
  stroke: #3b82f6;
  fill: #3b82f6;
  stroke-width: 2;
  cursor: crosshair;
}

:deep(.vue-flow__edge-updater:hover) {
  stroke: #2563eb;
  fill: #2563eb;
}

/* Edge control point styles for better visibility */
:deep(.edge-control-point) {
  cursor: move !important;
  opacity: 0;
  transition:
    opacity 0.2s ease,
    r 0.2s ease;
}

:deep(.vue-flow__edge.selected .edge-control-point) {
  opacity: 1;
}

:deep(.edge-control-point:hover) {
  r: 6 !important;
  fill: #2563eb !important;
}

/* Add custom node icon styles */
:deep(.custom-node) {
  border-radius: 6px;
  padding: 12px;
  background: white;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
  min-width: 150px;
}

:deep(.custom-node-header) {
  display: flex;
  align-items: center;
  margin-bottom: 8px;
}

:deep(.custom-node-icon) {
  margin-right: 8px;
}

:deep(.custom-node-icon .material-icons) {
  font-size: 20px;
}

:deep(.node-start .custom-node-icon .material-icons) {
  color: #4caf50;
}

:deep(.node-end .custom-node-icon .material-icons) {
  color: #f44336;
}

:deep(.node-task .custom-node-icon .material-icons) {
  color: #2196f3;
}

:deep(.node-form .custom-node-icon .material-icons) {
  color: #9c27b0;
}

:deep(.node-gateway .custom-node-icon .material-icons) {
  color: #ff9800;
  font-size: 18px;
}

:deep(.node-script .custom-node-icon .material-icons) {
  color: #607d8b;
}

:deep(.node-html .custom-node-icon .material-icons) {
  color: #0ea5e9;
}

:deep(.custom-node-title) {
  font-weight: 500;
  flex-grow: 1;
}

:deep(.custom-node-content) {
  font-size: 12px;
  color: #666;
}

@keyframes flowEdgeAnimation {
  from {
    stroke-dashoffset: 10;
  }
  to {
    stroke-dashoffset: 0;
  }
}

.node-controls {
  margin: 10px;
  color: #666;
  font-size: 12px;
  background: white;
  border-radius: 4px;
  pointer-events: all;
}

:deep(.vue-flow__handle) {
  width: 8px;
  height: 8px;
  background: #555;
  border: 2px solid white;
}

:deep(.vue-flow__handle:hover) {
  background: #ff6b6b;
}

:deep(.vue-flow__edge.selected .vue-flow__edge-path) {
  stroke: #ff6b6b;
  stroke-width: 3px;
}

:deep(.vue-flow__node.selected) {
  box-shadow: 0 0 0 2px #ff6b6b;
}

/* Target both Vue Flow nodes and custom nodes - updated highlighting */
:deep(.vue-flow__node.highlighted-node),
:deep(.vue-flow__node[data-highlighted="true"]),
:deep(.vue-flow__node .custom-node.highlighted-node),
:deep(.vue-flow__node .custom-node[data-highlighted="true"]) {
  animation: highlightPulse 2s ease-in-out infinite !important;
  box-shadow:
    0 0 0 3px #3b82f6,
    0 0 20px rgba(59, 130, 246, 0.4) !important;
  z-index: 999 !important;
  transform: scale(1.05) !important;
  transition: transform 0.3s ease-in-out !important;
}

/* Additional selector for direct custom node highlighting */
:deep(.custom-node.highlighted-node),
:deep(.custom-node[data-highlighted="true"]) {
  animation: highlightPulse 2s ease-in-out infinite !important;
  box-shadow:
    0 0 0 3px #3b82f6,
    0 0 20px rgba(59, 130, 246, 0.4) !important;
  z-index: 999 !important;
  transform: scale(1.05) !important;
  transition: transform 0.3s ease-in-out !important;
}

@keyframes highlightPulse {
  0%,
  100% {
    box-shadow:
      0 0 0 3px #3b82f6,
      0 0 20px rgba(59, 130, 246, 0.4);
  }
  50% {
    box-shadow:
      0 0 0 5px #60a5fa,
      0 0 30px rgba(96, 165, 250, 0.6);
  }
}
</style>
