<template>
  <div class="workflow-view">
    <div class="workflow-bg"></div>
    <div class="header">
      <!-- Back button disabled -->
      <!-- <button @click="goBack" class="back-button">
        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
          <path d="M19 12H5M5 12L12 19M5 12L12 5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
        </svg>
      </button> -->
      <h1 class="workflow-name">{{ workflowName }}</h1>
    </div>
    
    <div class="content">
      <!-- YAML Editor Tab -->
      <div v-if="activeTab === 'yaml'" class="yaml-editor">
        <div v-if="yamlParseError" class="yaml-error">
          YAML Parse Error: {{ yamlParseError }}
        </div>
        <textarea 
          v-model="yamlTextString" 
          class="yaml-textarea"
          :class="{ 'yaml-error-border': yamlParseError }"
          placeholder="Loading YAML content..."
          readonly
        ></textarea>
      </div>

      <!-- VueFlow Graph Tab -->
      <div
        v-if="activeTab === 'graph'"
        class="vueflow-container"
        ref="vueflowContainerRef"
      >
        <VueFlow
         v-model:nodes="nodes"
         v-model:edges="edges"
         :delete-key-code="false"
         :fit-view-on-init="true"
         class="vueflow-graph"
         @node-click="onNodeClick"
         @edge-click="onEdgeClick"
         @connect="onConnect"
         @node-drag-stop="onNodeDragStop"
         @pane-context-menu="onPaneContextMenu"
         @node-context-menu="onNodeContextMenu"
         @edge-context-menu="onEdgeContextMenu">
        <template #node-workflow-node="props">
          <WorkflowNode
            :id="props.id"
            :data="props.data"
            @hover="onNodeHover"
            @leave="onNodeLeave"
          />
        </template>
        <template #node-start-node="props">
          <StartNode :id="props.id" :data="props.data" />
        </template>
        <template #edge-workflow-edge="props">
          <WorkflowEdge
            :id="props.id"
            :source="props.source"
            :target="props.target"
            :source-x="props.sourceX"
            :source-y="props.sourceY"
            :target-x="props.targetX"
            :target-y="props.targetY"
            :source-position="props.sourcePosition"
            :target-position="props.targetPosition"
            :data="props.data"
            :marker-end="props.markerEnd"
            :animated="props.animated"
            :hovered-node-id="hoveredNodeId"
          />
        </template>
        <Background pattern-color="#aaa"/>
        <Controls position="bottom-right"/>
        </VueFlow>

        <!-- Right-click context menu inside VueFlow container -->
        <transition name="fade">
          <div
            v-if="contextMenuVisible"
            class="context-menu"
            :style="{ left: contextMenuX + 'px', top: contextMenuY + 'px' }"
            @click.stop
          >
            <!-- Pane context menu -->
            <template v-if="contextMenuType === 'pane'">
              <RichTooltip v-if="shouldShowTooltip" :content="helpContent.contextMenu.createNode" placement="right">
                <div
                  class="context-menu-item"
                  @click.stop="() => { hideContextMenu(); openCreateNodeModal(); }"
                >
                  Create Node
                </div>
              </RichTooltip>
              <div
                v-else
                class="context-menu-item"
                @click.stop="() => { hideContextMenu(); openCreateNodeModal(); }"
              >
                Create Node
              </div>
            </template>

            <!-- Node context menu -->
            <template v-else-if="contextMenuType === 'node'">
              <RichTooltip v-if="shouldShowTooltip" :content="helpContent.contextMenu.copyNode" placement="right">
                <div
                  class="context-menu-item"
                  @click.stop="() => { hideContextMenu(); onCopyNodeFromContext(); }"
                >
                  Copy Node
                </div>
              </RichTooltip>
              <div
                v-else
                class="context-menu-item"
                @click.stop="() => { hideContextMenu(); onCopyNodeFromContext(); }"
              >
                Copy Node
              </div>
              <RichTooltip v-if="shouldShowTooltip" :content="helpContent.contextMenu.deleteNode" placement="right">
                <div
                  class="context-menu-item"
                  @click.stop="() => { hideContextMenu(); onDeleteNodeFromContext(); }"
                >
                  Delete Node
                </div>
              </RichTooltip>
              <div
                v-else
                class="context-menu-item"
                @click.stop="() => { hideContextMenu(); onDeleteNodeFromContext(); }"
              >
                Delete Node
              </div>
            </template>

            <!-- Edge context menu -->
            <template v-else-if="contextMenuType === 'edge'">
              <RichTooltip v-if="shouldShowTooltip" :content="helpContent.contextMenu.deleteEdge" placement="right">
                <div
                  class="context-menu-item"
                  @click.stop="() => { hideContextMenu(); onDeleteEdgeFromContext(); }"
                >
                  Delete Edge
                </div>
              </RichTooltip>
              <div
                v-else
                class="context-menu-item"
                @click.stop="() => { hideContextMenu(); onDeleteEdgeFromContext(); }"
              >
                Delete Edge
              </div>
            </template>
          </div>
        </transition>
      </div>
    </div>

    <div class="tabs">
      <div class="tab-buttons">
        <button 
          :class="['tab', { active: activeTab === 'graph' }]"
          @click="activeTab = 'graph'"
        >
          Workflow Graph
        </button>
        <button 
          :class="['tab', { active: activeTab === 'yaml' }]"
          @click="activeTab = 'yaml'"
        >
          YAML Configuration
        </button>
      </div>
      <div v-if="activeTab === 'graph'" class="editor-actions">
        <RichTooltip v-if="shouldShowTooltip" :content="helpContent.contextMenu.createNodeButton" placement="bottom">
          <button @click="openCreateNodeModal" class="glass-button">
            <span>Create Node</span>
          </button>
        </RichTooltip>
        <button v-else @click="openCreateNodeModal" class="glass-button">
          <span>Create Node</span>
        </button>
        <RichTooltip v-if="shouldShowTooltip" :content="helpContent.contextMenu.configureGraph" placement="bottom">
          <button @click="openConfigureGraphModal" class="glass-button">
            <span>Configure Graph</span>
          </button>
        </RichTooltip>
        <button v-else @click="openConfigureGraphModal" class="glass-button">
          <span>Configure Graph</span>
        </button>
        <RichTooltip v-if="shouldShowTooltip" :content="helpContent.contextMenu.launch" placement="bottom">
          <button @click="goToLaunch" class="launch-button-primary">
            <span>Launch</span>
          </button>
        </RichTooltip>
        <button v-else @click="goToLaunch" class="launch-button-primary">
          <span>Launch</span>
        </button>
        
        <div
          class="menu-container"
          @mouseenter="showMenu = true"
          @mouseleave="showMenu = false"
        >
          <div
            class="menu-trigger"
            :class="{ 'menu-trigger-active': showMenu }"
          >
            <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
              <path d="M3 12H21M3 6H21M3 18H21" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
            </svg>
          </div>
          <transition name="fade">
            <div v-if="showMenu" class="menu-dropdown">
              <RichTooltip v-if="shouldShowTooltip" :content="helpContent.contextMenu.renameWorkflow" placement="left">
                <div @click="openRenameWorkflowModal" class="menu-item">Rename Workflow</div>
              </RichTooltip>
              <div v-else @click="openRenameWorkflowModal" class="menu-item">Rename Workflow</div>
              <RichTooltip v-if="shouldShowTooltip" :content="helpContent.contextMenu.copyWorkflow" placement="left">
                <div @click="openCopyWorkflowModal" class="menu-item">Copy Workflow</div>
              </RichTooltip>
              <div v-else @click="openCopyWorkflowModal" class="menu-item">Copy Workflow</div>
              <RichTooltip v-if="shouldShowTooltip" :content="helpContent.contextMenu.manageVariables" placement="left">
                <div @click="openManageVarsModal" class="menu-item">Manage Variables</div>
              </RichTooltip>
              <div v-else @click="openManageVarsModal" class="menu-item">Manage Variables</div>
              <RichTooltip v-if="shouldShowTooltip" :content="helpContent.contextMenu.manageMemories" placement="left">
                <div @click="openManageMemoriesModal" class="menu-item">Manage Memories</div>
              </RichTooltip>
              <div v-else @click="openManageMemoriesModal" class="menu-item">Manage Memories</div>
              <RichTooltip v-if="shouldShowTooltip" :content="helpContent.contextMenu.createEdge" placement="left">
                <div @click="openCreateEdgeModal" class="menu-item">Create Edge</div>
              </RichTooltip>
              <div v-else @click="openCreateEdgeModal" class="menu-item">Create Edge</div>
            </div>
          </transition>
        </div>
      </div>
    </div>

  </div>

  <FormGenerator
    v-if="showDynamicFormGenerator"
    :breadcrumbs="formGeneratorBreadcrumbs"
    :recursive="formGeneratorRecursive"
    :workflow-name="workflowName"
    :initial-yaml="formGeneratorInitialYaml ?? yamlContent"
    :initial-form-data="formGeneratorInitialFormData"
    :mode="formGeneratorMode"
    :field-filter="formGeneratorFieldFilter"
    :read-only-fields="formGeneratorReadOnlyFields"
    @close="closeDynamicFormGenerator"
    @submit="handleFormGeneratorSubmit"
    @copy="handleFormGeneratorCopy"
  />

  <!-- Rename Workflow Modal -->
  <div v-if="showRenameModal" class="modal-overlay" @click.self="closeRenameModal">
    <div class="modal-content">
      <div class="modal-header">
        <h3 class="modal-title">Rename Workflow</h3>
        <button @click="closeRenameModal" class="close-button">
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
          </svg>
        </button>
      </div>
      <div class="modal-body">
        <div class="form-group">
          <label for="rename-workflow-name" class="form-label">Workflow Name</label>
          <input
            id="rename-workflow-name"
            v-model="renameWorkflowName"
            type="text"
            class="form-input"
            placeholder="Enter new workflow name"
            @keyup.enter="handleRenameSubmit"
          />
        </div>
      </div>
      <div class="modal-footer">
        <button @click="closeRenameModal" class="cancel-button">Cancel</button>
        <button @click="handleRenameSubmit" class="submit-button" :disabled="!renameWorkflowName.trim()">Submit</button>
      </div>
    </div>
  </div>

  <!-- Copy Workflow Modal -->
  <div v-if="showCopyModal" class="modal-overlay" @click.self="closeCopyModal">
    <div class="modal-content">
      <div class="modal-header">
        <h3 class="modal-title">Copy Workflow</h3>
        <button @click="closeCopyModal" class="close-button">
          <svg width="18" height="18" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
            <path d="M18 6L6 18M6 6L18 18" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
          </svg>
        </button>
      </div>
      <div class="modal-body">
        <div class="form-group">
          <label for="copy-workflow-name" class="form-label">Workflow Name</label>
          <input
            id="copy-workflow-name"
            v-model="copyWorkflowName"
            type="text"
            class="form-input"
            placeholder="Enter new workflow name"
            @keyup.enter="handleCopySubmit"
          />
        </div>
      </div>
      <div class="modal-footer">
        <button @click="closeCopyModal" class="cancel-button">Cancel</button>
        <button @click="handleCopySubmit" class="submit-button" :disabled="!copyWorkflowName.trim()">Submit</button>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, watch, nextTick, onMounted, onBeforeUnmount, computed } from 'vue'
import { useRouter } from 'vue-router'
import { VueFlow, useVueFlow, MarkerType } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import { Controls } from '@vue-flow/controls'
import '@vue-flow/core/dist/style.css'
import '@vue-flow/controls/dist/style.css'
import '../utils/vueflow.css'
import WorkflowNode from '../components/WorkflowNode.vue'
import WorkflowEdge from '../components/WorkflowEdge.vue'
import StartNode from '../components/StartNode.vue'
import FormGenerator from '../components/FormGenerator.vue'
import RichTooltip from '../components/RichTooltip.vue'
import yaml from 'js-yaml'
import { fetchYaml, fetchVueGraph, postVuegraphs, updateYaml, postYamlNameChange, postYamlCopy } from '../utils/apiFunctions'
import { helpContent } from '../utils/helpContent.js'
import { configStore } from '../utils/configStore.js'

const shouldShowTooltip = computed(() => configStore.ENABLE_HELP_TOOLTIPS)

const props = defineProps({
  workflowName: {
    type: String,
    required: true
  }
})
const emit = defineEmits(['refresh-workflows'])
const router = useRouter()
const { toObject, fromObject, fitView, getViewport } = useVueFlow()

const vueflowContainerRef = ref(null)
// Hovered node id for highlighting related edges
const hoveredNodeId = ref(null)

const onNodeHover = (nodeId) => {
  hoveredNodeId.value = nodeId || null
}
const onNodeLeave = (_nodeId) => {
  hoveredNodeId.value = null
}

const workflowName = ref('')
const activeTab = ref('graph')
const yamlContent = ref({}) // YAML object
const yamlTextString = ref('') // YAML string
const yamlParseError = ref(null)

const showDynamicFormGenerator = ref(false)
const showMenu = ref(false)
const formGeneratorBreadcrumbs = ref([])
const formGeneratorRecursive = ref(false)
const formGeneratorInitialYaml = ref(null)
const formGeneratorInitialFormData = ref(null)
const formGeneratorMode = ref('create')
const formGeneratorFieldFilter = ref([])
const formGeneratorReadOnlyFields = ref([])

// Modal states for rename and copy
const showRenameModal = ref(false)
const showCopyModal = ref(false)
const renameWorkflowName = ref('')
const copyWorkflowName = ref('')

const FORM_GENERATOR_CONFIG = Object.freeze({
  graph: [
    { node: 'DesignConfig', field: 'graph' }
  ],
  node: [
    { node: 'DesignConfig', field: 'graph' },
    { node: 'GraphDefinition', field: 'nodes' }
  ],
  edge: [
    { node: 'DesignConfig', field: 'graph' },
    { node: 'GraphDefinition', field: 'edges' }
  ],
  memory: [
    { node: 'DesignConfig', field: 'graph' }
  ],
  vars: [
    // Empty breadcrumbs for managing global vars
  ]
})

const cloneDeep = (value) => {
  if (value === null || value === undefined) {
    return null
  }
  if (typeof value === 'object') {
    return JSON.parse(JSON.stringify(value))
  }
  return value
}

// VueFlow nodes and edges
const nodes = ref([])

const edges = ref([])

const isCreatingConnection = ref(false)

// Start node ID
const START_NODE_ID = '__start'

// Context menu state
const contextMenuVisible = ref(false)
const contextMenuX = ref(0)
const contextMenuY = ref(0)
const pendingNodePosition = ref(null)
const contextMenuType = ref('pane') // 'pane' | 'node' | 'edge'
const contextNodeId = ref(null)
const contextEdgeInfo = ref(null) // { from, to }

const hideContextMenu = () => {
  contextMenuVisible.value = false
}

const getFlowPositionFromEvent = (event) => {
  try {
    const viewport = getViewport()
    const container = vueflowContainerRef.value
    if (!viewport || !container || !event) {
      return getCentralPosition()
    }

    const rect = container.getBoundingClientRect()
    const screenX = event.clientX - rect.left
    const screenY = event.clientY - rect.top

    const x = (screenX - viewport.x) / viewport.zoom
    const y = (screenY - viewport.y) / viewport.zoom

    return { x, y }
  } catch (error) {
    console.warn('Failed to convert click position to flow coordinates:', error)
    return getCentralPosition()
  }
}

const onContainerContextMenu = (event) => {
  // Only respond to right-clicks inside the flow container
  if (!vueflowContainerRef.value) {
    return
  }

  // Store menu position relative to container (for UI)
  const rect = vueflowContainerRef.value.getBoundingClientRect()
  contextMenuX.value = event.clientX - rect.left
  contextMenuY.value = event.clientY - rect.top
  contextMenuType.value = 'pane'
  contextNodeId.value = null
  contextEdgeInfo.value = null
  contextMenuVisible.value = true

  // Pre-compute desired node position in flow coordinates
  pendingNodePosition.value = getFlowPositionFromEvent(event)
}

// Disable default pane context behaviour in VueFlow and use custom behaviour
const onPaneContextMenu = (params) => {
  const mouseEvent = params?.event || params
  mouseEvent?.preventDefault?.()
  if (!mouseEvent) return
  onContainerContextMenu(mouseEvent)
}

const onNodeContextMenu = (params) => {
  const mouseEvent = params?.event || params
  const node = params?.node || params?.event?.node || params
  if (!mouseEvent || !node?.id || !vueflowContainerRef.value) {
    return
  }
  mouseEvent.preventDefault?.()
  
  // Ignore context menu for start node, show dim animation
  if (node.id === START_NODE_ID) {
    dimStartNode()
    return
  }

  const rect = vueflowContainerRef.value.getBoundingClientRect()
  contextMenuX.value = mouseEvent.clientX - rect.left
  contextMenuY.value = mouseEvent.clientY - rect.top

  contextMenuType.value = 'node'
  contextNodeId.value = node.id
  contextEdgeInfo.value = null
  pendingNodePosition.value = null
  contextMenuVisible.value = true
}

const onEdgeContextMenu = (params) => {
  const mouseEvent = params?.event || params
  const edge = params?.edge || params?.event?.edge || params
  if (!mouseEvent || !edge || !vueflowContainerRef.value) {
    return
  }
  mouseEvent.preventDefault?.()

  const fromId = edge.data?.from || edge.source
  const toId = edge.data?.to || edge.target
  if (!fromId || !toId) {
    return
  }

  const rect = vueflowContainerRef.value.getBoundingClientRect()
  contextMenuX.value = mouseEvent.clientX - rect.left
  contextMenuY.value = mouseEvent.clientY - rect.top

  contextMenuType.value = 'edge'
  contextNodeId.value = null
  contextEdgeInfo.value = { from: fromId, to: toId }
  pendingNodePosition.value = null
  contextMenuVisible.value = true
}

const onGlobalClick = () => {
  // Clicks outside the menu will bubble to window and handled here
  // Clicks on the menu itself are stopped with @click.stop
  if (contextMenuVisible.value) {
    contextMenuVisible.value = false
  }
}

const dimStartNode = () => {
  const startNode = nodes.value.find(node => node.id === START_NODE_ID)
  if (!startNode) return

  const dimSpeed = 120 // milliseconds between opacity changes - slightly slower for smoothness
  const dimSteps = [1, 0.6, 1, 0.6, 1] // Full opacity -> dim -> full -> dim -> full

  dimSteps.forEach((opacity, index) => {
    setTimeout(() => {
      // Add opacity to node data for animation
      startNode.data = { ...startNode.data, opacity }
      
      // Force VueFlow to update by triggering reactivity
      nodes.value = [...nodes.value]
    }, index * dimSpeed)
  })
}

// Persist an updated YAML snapshot back to the server and refresh local state
const persistYamlSnapshot = async (snapshot) => {
  try {
    if (!workflowName.value) {
      return false
    }
    const yamlString = yaml.dump(snapshot ?? {})
    const result = await updateYaml(workflowName.value, yamlString)
    if (!result?.success) {
      console.error('Failed to update workflow YAML:', result?.message || result?.detail)
      return false
    }
    console.log("YAML snapshot successfully persisted through API")
    return true
  } catch (error) {
    console.error('Error persisting YAML snapshot:', error)
    return false
  }
}

const onCopyNodeFromContext = () => {
  const nodeId = contextNodeId.value
  if (!nodeId) {
    return
  }
  const yamlNodeContent = (yamlContent.value?.graph?.nodes || []).find(node => node.id === nodeId)
  if (!yamlNodeContent) {
    console.warn(`[WorkflowView] Node with id "${nodeId}" not found for copying`)
    return
  }
  handleFormGeneratorCopy({ initialFormData: yamlNodeContent })
}

const deleteNodeById = async (nodeId) => {
  if (!nodeId) {
    return
  }
  const source = yamlContent.value
  if (!source?.graph) {
    return
  }
  const sourceGraph = source.graph
  const nodesArr = Array.isArray(sourceGraph.nodes) ? sourceGraph.nodes : []
  const edgesArr = Array.isArray(sourceGraph.edges) ? sourceGraph.edges : []

  // Remove the node and its related edges
  const nextNodes = nodesArr.filter(node => node?.id !== nodeId)
  const nextEdges = edgesArr.filter(edge => edge?.from !== nodeId && edge?.to !== nodeId)
  
  // Remove node ID from graph.start/end
  const nextStart = Array.isArray(sourceGraph.start)
    ? sourceGraph.start.filter(id => id !== nodeId)
    : sourceGraph.start
  const nextEnd = Array.isArray(sourceGraph.end)
    ? sourceGraph.end.filter(id => id !== nodeId)
    : sourceGraph.end

  const nextSnapshot = {
    ...source,
    graph: {
      ...sourceGraph,
      nodes: nextNodes,
      edges: nextEdges,
      start: nextStart,
      end: nextEnd
    }
  }

  const ok = await persistYamlSnapshot(nextSnapshot)
  if (!ok) {
    return
  }

  await loadYamlFile()
  syncVueNodesAndEdgesData()
  await nextTick()
  await saveVueFlowGraph()
}

const deleteEdgeByEndpoints = async (fromId, toId) => {
  if (!fromId || !toId) {
    return
  }
  const source = yamlContent.value
  if (!source?.graph || !Array.isArray(source.graph.edges)) {
    return
  }
  const sourceGraph = source.graph

  let removed = false
  const nextEdges = sourceGraph.edges.filter(edge => {
    if (!removed && edge?.from === fromId && edge?.to === toId) {
      removed = true
      return false
    }
    return true
  })

  // Delete from .start if edge is from Start Node
  let nextStart = sourceGraph.start
  if (fromId === START_NODE_ID) {
    nextStart = Array.isArray(sourceGraph.start)
      ? sourceGraph.start.filter(id => id !== toId)
      : sourceGraph.start

    // Empty start node array is not allowed
    const startArray = Array.isArray(nextStart) ? nextStart : []
    if (startArray.length === 0) {
      alert("At least one connection required from start node!")
      return
    }
  }

  const nextSnapshot = {
    ...source,
    graph: {
      ...sourceGraph,
      edges: nextEdges,
      start: nextStart
    }
  }

  const ok = await persistYamlSnapshot(nextSnapshot)
  if (!ok) {
    return
  }

  await loadYamlFile()
  syncVueNodesAndEdgesData()
  await nextTick()
  await saveVueFlowGraph()
}

const onDeleteNodeFromContext = async () => {
  const nodeId = contextNodeId.value
  contextNodeId.value = null
  const confirmed = window.confirm(`Are you sure you want to delete this node?`)
  if (!confirmed) {
    return
  }
  if (!nodeId) {
    return
  }
  await deleteNodeById(nodeId)
}

const onDeleteEdgeFromContext = async () => {
  const info = contextEdgeInfo.value
  contextEdgeInfo.value = null
  const confirmed = window.confirm(`Are you sure you want to delete this edge?`)
  if (!confirmed) {
    return
  }
  if (!info?.from || !info?.to) {
    return
  }
  await deleteEdgeByEndpoints(info.from, info.to)
}

const initializeWorkflow = async (name) => {
  if (!name) {
    return
  }
  workflowName.value = name
  console.log('Workflow initialized: ', workflowName.value)
  await loadYamlFile()
  loadAndSyncVueFlowGraph()
  await nextTick()
  fitView?.({ padding: 0.1 })
}

watch(
  () => props.workflowName,
  async (newName) => {
    await initializeWorkflow(newName)
  },
  { immediate: false }
)

onMounted(async () => {
  window.addEventListener('click', onGlobalClick)
  await initializeWorkflow(props.workflowName)
})

onBeforeUnmount(() => {
  window.removeEventListener('click', onGlobalClick)
})

watch(activeTab, async (newTab) => {
  if (newTab === 'graph') {
    await nextTick()
    fitView?.({ padding: 0.1 })
  }
})

const saveVueFlowGraph = async () => {
  try {
    const flowObj = toObject()
    const key = workflowName.value
    const result = await postVuegraphs({
      filename: key,
      content: JSON.stringify(flowObj)
    })

    if (!result?.success) {
      console.error('Failed to save VueFlow graph:', result?.message || result?.detail)
      return false
    }
    return true
  } catch (error) {
    console.error('Failed to save VueFlow graph:', error)
    return false
  }
}

const loadAndSyncVueFlowGraph = async () => {
  try {
    const key = workflowName.value
    const result = await fetchVueGraph(key)

    if(result?.success === true) {
      console.log("Graph fetched successfully")
    }

    if (result?.status === 404) {
      // Not found in server storage, fallback
      console.log("No graph found, fallback to generation")
      await generateNodesAndEdges()
      return false
    }

    if (!result?.success) {
      console.error('Failed to load VueFlow graph:', result?.message || result?.detail)
      // Fallback if server error
      generateNodesAndEdges()
      return false
    }

    const content = result?.content
    if (content) {
      const flow = JSON.parse(content)
      if (flow) {
        fromObject(flow)
        await nextTick()
        syncVueNodesAndEdgesData()
        return true
      }
    }
  } catch (error) {
    console.error('Failed to load saved VueFlow graph:', error)
  }

  // If no VueFlow graph restored, fall back to manual generation
  await generateNodesAndEdges()
  return false
}

const loadYamlFile = async () => {
  try {
    if (!workflowName.value) {
      return
    }
    const result = await fetchYaml(workflowName.value)

    if (!result?.success) {
      console.error('Failed to load YAML file', result?.message || result?.detail)
      return
    }

    const yamlString = result.content ?? ''
    console.log('YAML content loaded successfully')

    // Parse YAML string to YAML object
    try {
      const parsed = yaml.load(yamlString)
      yamlContent.value = parsed || {}
      yamlTextString.value = yamlString
      yamlParseError.value = null
    } catch (parseError) {
      console.error('Error parsing YAML:', parseError)
      yamlParseError.value = parseError.message
      yamlTextString.value = yamlString
    }
  } catch (error) {
    console.error('Error loading YAML file: ', error)
  }
}

const getCentralPosition = () => {
  try {
    const viewport = getViewport()
    if (viewport && vueflowContainerRef.value) {
      // Get container dimensions
      const container = vueflowContainerRef.value
      const containerWidth = container.clientWidth || container.offsetWidth
      const containerHeight = container.clientHeight || container.offsetHeight
      
      const screenCenterX = containerWidth / 2
      const screenCenterY = containerHeight / 2
      
      // Convert screen coordinates to flow coordinates
      // Formula: flowCoord = (screenCoord - viewportOffset) / zoom
      const centerX = (screenCenterX - viewport.x) / viewport.zoom
      const centerY = (screenCenterY - viewport.y) / viewport.zoom
      
      return { x: centerX, y: centerY }
    }
  } catch (error) {
    console.warn('Failed to get viewport center, using default position:', error)
  }
  // Fallback to default center position
  return { x: 400, y: 300 }
}

const updateNodesAndEdgesFromYaml = (preserveExistingLayout = false) => {
  try {
    const yamlNodes = Array.isArray(yamlContent.value?.graph?.nodes)
      ? yamlContent.value.graph.nodes
      : []
    const yamlEdges = Array.isArray(yamlContent.value?.graph?.edges)
      ? yamlContent.value.graph.edges
      : []

    const currentNodes = nodes.value || []
    const currentEdges = edges.value || []
    const defaultCenterPosition = getCentralPosition()
    const getDefaultCenterPosition = () => ({
      x: defaultCenterPosition.x,
      y: defaultCenterPosition.y
    })

    const existingNodeById = preserveExistingLayout
      ? new Map(currentNodes.map(node => [node.id, node]))
      : null
    const existingEdgeByKey = preserveExistingLayout
      ? new Map(currentEdges.map(edge => [`${edge.source}-${edge.target}`, edge]))
      : null
    
      // Compute node positions using a simple topological layering
      // This arranges nodes by levels (distance from sources)
      // Ignore backward/cycle edges for cyclic graphs
      try {
        const nodeIds = (yamlNodes || []).map(n => n?.id).filter(Boolean)

        // Build adjacency and indegree
        const adj = new Map()
        const indegree = new Map()
        nodeIds.forEach(id => {
          adj.set(id, new Set())
          indegree.set(id, 0)
        })

        ;(yamlEdges || []).forEach(e => {
          if (!e || !e.from || !e.to) return
          if (!adj.has(e.from) || !adj.has(e.to)) return
          adj.get(e.from).add(e.to)
          indegree.set(e.to, (indegree.get(e.to) || 0) + 1)
        })

        // Kahn's algorithm to compute levels
        const levelById = new Map()
        const q = []
        nodeIds.forEach(id => {
          if ((indegree.get(id) || 0) === 0) {
            q.push(id)
            levelById.set(id, 0)
          }
        })

        // Heuristic: if the graph has no indegree-0 nodes (pure cycle),
        // pick the first node declared in YAML `graph.start` as a pseudo-source
        // so Kahn's algorithm can proceed and assign at least one level.
        if (q.length === 0) {
          try {
            const yamlStartList = Array.isArray(yamlContent.value?.graph?.start)
              ? yamlContent.value.graph.start
              : []
            const firstStart = yamlStartList.find(s => nodeIds.includes(s))
            if (firstStart) {
              // Force indegree to zero and seed the queue so at least one node gets level 0
              indegree.set(firstStart, 0)
              q.push(firstStart)
              levelById.set(firstStart, 0)
            }
          } catch (e) {
            // ignore and fall back later
          }
        }

        let queueIndex = 0
        while (queueIndex < q.length) {
          const id = q[queueIndex++]
          const baseLevel = levelById.get(id) || 0
          const neighbors = adj.get(id) || new Set()
          for (const nb of neighbors) {
            const prev = levelById.get(nb) ?? 0
            const newLevel = Math.max(prev, baseLevel + 1)
            levelById.set(nb, newLevel)
            indegree.set(nb, indegree.get(nb) - 1)
            if (indegree.get(nb) === 0) q.push(nb)
          }
        }

        
        const predecessors = new Map()
        nodeIds.forEach(id => predecessors.set(id, new Set()))
        ;(yamlEdges || []).forEach(e => {
          if (!e || !e.from || !e.to) return
          if (!predecessors.has(e.to)) return
          predecessors.get(e.to).add(e.from)
        })

        let changed = true
        let iterations = 0
        const maxIterations = nodeIds.length + 5
        while (changed && iterations < maxIterations) {
          changed = false
          iterations++
          nodeIds.forEach(id => {
            if (levelById.has(id)) return
            const preds = predecessors.get(id) || new Set()
            const predLevels = Array.from(preds).map(p => levelById.get(p)).filter(l => typeof l === 'number')
            if (predLevels.length) {
              const lvl = Math.max(...predLevels) + 1
              levelById.set(id, lvl)
              changed = true
            }
          })
        }

        // Any remaining unassigned nodes -> fallback to level 0
        nodeIds.forEach(id => {
          if (!levelById.has(id)) levelById.set(id, 0)
        })

        // Group nodes by level and compute positions (simple grid per level)
        const buckets = new Map()
        for (const [id, lvl] of levelById.entries()) {
          if (!buckets.has(lvl)) buckets.set(lvl, [])
          buckets.get(lvl).push(id)
        }

        const positions = new Map()
        const levelKeys = Array.from(buckets.keys()).sort((a, b) => a - b)
        const spacingX = 280
        const spacingY = 120
        const startX = 50
        const startY = 50

        levelKeys.forEach(lvl => {
          const ids = buckets.get(lvl) || []
          const x = startX + lvl * spacingX
          let currentY = startY
          // Apply a slight Y offset (+/-10) to the first node in each layer to avoid exact horizontal alignments
          const layerOffset = (lvl % 2 === 0) ? -30 : 30
          ids.forEach((id, idx) => {
            const yPos = currentY + (idx === 0 ? layerOffset : 0)
            positions.set(id, { x, y: yPos })
            currentY += spacingY
          })
        })

        // Build nextNodes respecting preserveExistingLayout where possible
        const nextNodes = yamlNodes.map((yamlNode, index) => {
          const id = yamlNode?.id
          if (!id) return null
          if (preserveExistingLayout && existingNodeById?.has(id)) {
            const existingNode = existingNodeById.get(id)
            return {
              ...existingNode,
              id,
              label: id,
              data: yamlNode
            }
          }

          const pos = positions.get(id) || getDefaultCenterPosition()
          return {
            id,
            type: 'workflow-node',
            label: id,
            position: pos,
            data: yamlNode
          }
        }).filter(Boolean)

        nodes.value = nextNodes
      } catch (err) {
        console.error('Failed to compute topological layout, falling back to center positions:', err)
        // Fallback to previous behavior
        const nextNodes = yamlNodes.map((yamlNode, index) => ({
          id: yamlNode.id,
          type: 'workflow-node',
          label: yamlNode.id,
          position: getDefaultCenterPosition(),
          data: yamlNode
        }))
        nodes.value = nextNodes
      }

    // Build edges from YAML (preserve layout where possible)
    const nextYamlEdges = yamlEdges.map(yamlEdge => {
      const key = `${yamlEdge.from}-${yamlEdge.to}`
      const baseEdge = preserveExistingLayout && existingEdgeByKey?.has(key)
        ? existingEdgeByKey.get(key)
        : {
            id: key,
            source: yamlEdge.from,
            target: yamlEdge.to,
            type: 'workflow-edge'
          }

      return {
        ...baseEdge,
        id: key,
        source: yamlEdge.from,
        target: yamlEdge.to,
        data: yamlEdge,
        markerEnd: {
          type: MarkerType.Arrow,
          width: 16,
          height: 16,
          // Set color to match with edge
          color: (yamlEdge && yamlEdge.trigger === false) ? '#868686' : '#f2f2f2',
          strokeWidth: 1.5,
        },
      }
    })
    // Nodes in .start
    const declaredStartSet = new Set(Array.isArray(yamlContent.value?.graph?.start) ? yamlContent.value.graph.start : [])

    // Create visual-only start node (reuse existing if present)
    let startNode = null
    if (preserveExistingLayout && existingNodeById?.has(START_NODE_ID)) {
      startNode = { ...existingNodeById.get(START_NODE_ID), id: START_NODE_ID, type: 'start-node', data: { id: START_NODE_ID, label: 'Start' } }
    } else {
      try {
        // Place start node to the left of the leftmost column
        const yamlNodesInGraph = (nodes.value || []).filter(n => n && n.id !== START_NODE_ID)
        if (yamlNodesInGraph.length) {
          const xs = yamlNodesInGraph.map(n => (n?.position && typeof n.position.x === 'number') ? n.position.x : defaultCenterPosition.x)
          const minX = Math.min(...xs)
          // Find nodes in that left column
          const tol = 1
          const leftColumn = yamlNodesInGraph.filter(n => Math.abs((n?.position?.x || 0) - minX) <= tol)
          const ys = leftColumn.map(n => (n?.position && typeof n.position.y === 'number') ? n.position.y : defaultCenterPosition.y)
          const avgY = ys.length ? ys.reduce((a, b) => a + b, 0) / ys.length : defaultCenterPosition.y
          const startXOffset = -100
          const startYOffset = 80
          startNode = {
            id: START_NODE_ID,
            type: 'start-node',
            label: 'Start',
            position: { x: minX + startXOffset, y: avgY + startYOffset },
            data: { id: START_NODE_ID, label: 'Start' }
          }
        } else {
          startNode = {
            id: START_NODE_ID,
            type: 'start-node',
            label: 'Start',
            position: getDefaultCenterPosition(),
            data: { id: START_NODE_ID, label: 'Start' }
          }
        }
      } catch (err) {
        console.warn('Failed to compute start node position, falling back to center:', err)
        startNode = {
          id: START_NODE_ID,
          type: 'start-node',
          label: 'Start',
          position: getDefaultCenterPosition(),
          data: { id: START_NODE_ID, label: 'Start' }
        }
      }
    }

    // Build start edges to YAML nodes that are declared in graph.start
    const startEdges = (yamlNodes || []).map(yamlNode => {
      if (!yamlNode?.id) return null
      if (!declaredStartSet.has(yamlNode.id)) return null

      const key = `${START_NODE_ID}-${yamlNode.id}`
      const baseEdge = preserveExistingLayout && existingEdgeByKey?.has(key)
        ? existingEdgeByKey.get(key)
        : {
            id: key,
            source: START_NODE_ID,
            target: yamlNode.id,
            type: 'workflow-edge'
          }

        return {
        ...baseEdge,
        id: key,
        source: START_NODE_ID,
        target: yamlNode.id,
        data: { from: START_NODE_ID, to: yamlNode.id },
        markerEnd: {
          type: MarkerType.Arrow,
          width: 16,
          height: 16,
          color: '#f2f2f2',
          strokeWidth: 1.5,
        },
        animated: false
      }
    }).filter(Boolean)

    // Combine YAML edges with visual start edges (preserve any existing non-yaml edges)
    const nextYamlEdgeIdSet = new Set(nextYamlEdges.map(edge => edge.id))
    edges.value = [
      // keep any existing edges that are not YAML edges (e.g., visual-only) when preserving layout
      // but always exclude previous Start edges so they are replaced by the newly computed ones
      ...(preserveExistingLayout ? currentEdges.filter(e => {
        const k = `${e.source}-${e.target}`
        // drop if it's a YAML-defined edge or a previous Start edge
        const isYamlEdge = nextYamlEdgeIdSet.has(k)
        const isStartEdge = e.source === START_NODE_ID
        // Also drop if it looks like a YAML edge (has data.from/to) but isn't in nextYamlEdges (stale)
        const isStaleYamlEdge = e.data?.from && e.data?.to
        return !isYamlEdge && !isStartEdge && !isStaleYamlEdge
      }) : []),
      ...nextYamlEdges,
      ...startEdges
    ]

    // Ensure start node is present in nodes list (preserving layout if asked)
    if (!nodes.value.some(n => n.id === START_NODE_ID)) {
      nodes.value = [startNode, ...nodes.value]
    } else {
      // if present, ensure the start node data/type is correct
      nodes.value = nodes.value.map(n => n.id === START_NODE_ID ? startNode : n)
    }
  } catch (error) {
    console.error('Error syncing nodes and edges from YAML:', error)
  }
}

const generateNodesAndEdges = async (options = {}) => {
  updateNodesAndEdgesFromYaml(false)

  // Save generated graph at nextTick
  try {
    await nextTick()
    if (options.fit) {
      fitView?.({ padding: 0.1 })
    }
    await saveVueFlowGraph()
  } catch (err) {
    console.warn('Failed to persist generated VueFlow graph:', err)
  }
}

const syncVueNodesAndEdgesData = () => {
  updateNodesAndEdgesFromYaml(true)
}

const updateVueFlowNodeId = (oldId, newId) => {
  if (!oldId || !newId || oldId === newId) {
    return
  }

  nodes.value = (nodes.value || []).map(node => {
    if (node.id !== oldId) {
      return node
    }
    return {
      ...node,
      id: newId,
      label: newId,
      data: node.data
        ? { ...node.data, id: newId }
        : { id: newId }
    }
  })

  edges.value = (edges.value || []).map(edge => {
    let source = edge.source
    let target = edge.target
    let edgeChanged = false

    if (source === oldId) {
      source = newId
      edgeChanged = true
    }

    if (target === oldId) {
      target = newId
      edgeChanged = true
    }

    let nextData = edge.data
    if (edge.data) {
      const nextFrom = edge.data.from === oldId ? newId : edge.data.from
      const nextTo = edge.data.to === oldId ? newId : edge.data.to

      if (nextFrom !== edge.data.from || nextTo !== edge.data.to) {
        nextData = {
          ...edge.data,
          from: nextFrom,
          to: nextTo
        }
      }
    }

    const nextEdge = {
      ...edge,
      source,
      target,
      data: nextData
    }

    if (edgeChanged) {
      nextEdge.id = `${source}-${target}`
    }

    return nextEdge
  })

  // Update node ID in graph.start
  if (yamlContent.value?.graph?.start && Array.isArray(yamlContent.value.graph.start)) {
    yamlContent.value.graph.start = yamlContent.value.graph.start.map(startNodeId =>
      startNodeId === oldId ? newId : startNodeId
    )
  }

  // Same for graph.end
  if (yamlContent.value?.graph?.end && Array.isArray(yamlContent.value.graph.end)) {
    yamlContent.value.graph.end = yamlContent.value.graph.end.map(endNodeId =>
      endNodeId === oldId ? newId : endNodeId
    )
  }
}

// FormGenerator integration
// Build YAML without specific node (shallow clone path to avoid full deep-clone on editor open)
const buildYamlWithoutNode = (nodeId) => {
  const source = yamlContent.value
  if (!source?.graph?.nodes || !Array.isArray(source.graph.nodes)) {
    return source
  }
  return {
    ...source,
    graph: {
      ...source.graph,
      nodes: source.graph.nodes.filter(node => node?.id !== nodeId)
    }
  }
}

const buildYamlWithoutEdge = (fromId, toId) => {
  const source = yamlContent.value
  if (!source?.graph?.edges || !Array.isArray(source.graph.edges)) {
    return source
  }
  let removed = false
  const filteredEdges = source.graph.edges.filter(edge => {
    if (!removed && edge?.from === fromId && edge?.to === toId) {
      removed = true
      return false
    }
    return true
  })
  return {
    ...source,
    graph: {
      ...source.graph,
      edges: filteredEdges
    }
  }
}

const buildYamlWithoutVars = () => {
  const source = yamlContent.value
  if (!source || typeof source !== 'object') {
    return source
  }
  if (!Object.prototype.hasOwnProperty.call(source, 'vars')) {
    return source
  }
  const sanitized = { ...source }
  delete sanitized.vars
  return sanitized
}

const buildYamlWithoutMemory = () => {
  const source = yamlContent.value
  if (!source?.graph) {
    return source
  }
  if (Object.prototype.hasOwnProperty.call(source.graph, 'memory')) {
    const newGraph = { ...source.graph }
    delete newGraph.memory
    return {
      ...source,
      graph: newGraph
    }
  }
  return source
}

const buildYamlWithoutGraph = () => {
  const source = yamlContent.value
  if (!source || typeof source !== 'object') {
    return source
  }
  if (!Object.prototype.hasOwnProperty.call(source, 'graph')) {
    return source
  }
  const sanitized = { ...source }
  delete sanitized.graph
  return sanitized
}

const autoAddStartEdge = async (nextNodeId) => {
  const workflowNodes = (yamlContent.value?.graph?.nodes || []).filter(node => node?.id !== START_NODE_ID)
  if (workflowNodes.length === 1 && workflowNodes[0]?.id === nextNodeId) {
    const source = yamlContent.value
    const sourceGraph = source?.graph && typeof source.graph === 'object' ? source.graph : {}
    const currentStart = Array.isArray(sourceGraph.start) ? sourceGraph.start : []
    if (!currentStart.includes(nextNodeId)) {
      const nextSnapshot = {
        ...source,
        graph: {
          ...sourceGraph,
          start: [...currentStart, nextNodeId]
        }
      }
      const ok = await persistYamlSnapshot(nextSnapshot)
      if (ok) {
        await loadYamlFile()
        syncVueNodesAndEdgesData()
        await nextTick()
      }
    }
  }
}

const openDynamicFormGenerator = (type, options = {}) => {
  const config = FORM_GENERATOR_CONFIG[type]
  if (!config) {
    console.error(`[FormGenerator] Unknown type: ${type}`)
    return
  }
  formGeneratorBreadcrumbs.value = config.map(crumb => ({ ...crumb }))
  formGeneratorRecursive.value = options.recursive ?? true

  const resolvedMode = typeof options.mode === 'string' && ['create', 'edit'].includes(options.mode)
    ? options.mode
    : (options.initialFormData ? 'edit' : 'create')
  formGeneratorMode.value = resolvedMode

  const hasCustomYaml = Object.prototype.hasOwnProperty.call(options, 'initialYaml')
  const yamlSource = hasCustomYaml ? options.initialYaml : yamlContent.value
  formGeneratorInitialYaml.value = yamlSource || null

  if (Object.prototype.hasOwnProperty.call(options, 'initialFormData')) {
    formGeneratorInitialFormData.value = options.initialFormData || null
  } else {
    formGeneratorInitialFormData.value = null
  }

  formGeneratorFieldFilter.value = options.fieldFilter ?? []
  formGeneratorReadOnlyFields.value = options.readOnlyFields ?? []

  showDynamicFormGenerator.value = true
}

const closeDynamicFormGenerator = () => {
  showDynamicFormGenerator.value = false
  formGeneratorBreadcrumbs.value = []
  formGeneratorInitialYaml.value = null
  formGeneratorInitialFormData.value = null
  formGeneratorMode.value = 'create'
  formGeneratorFieldFilter.value = null
  formGeneratorReadOnlyFields.value = []
}

const handleFormGeneratorSubmit = async (payload) => {
  try {
    const previousNodeId = formGeneratorInitialFormData.value?.id
    const nextNodeId = payload?.rawFormData?.id

    //Update VueFlow node ID based on updated YAML if change present
    if (previousNodeId && nextNodeId && previousNodeId !== nextNodeId) {
      updateVueFlowNodeId(previousNodeId, nextNodeId)
    }

    await loadYamlFile()
    syncVueNodesAndEdgesData()
    // Ensure VueFlow internal state is updated from v-model bindings
    // before taking a snapshot to be saved into vuegraphs.db
    await nextTick()

    // If we opened the FormGenerator from a right-click context menu while creating
    // a new node, place that node at the stored position.
    if (formGeneratorMode.value === 'create' && pendingNodePosition.value && nextNodeId) {
      const newNode = (nodes.value || []).find(node => node.id === nextNodeId)
      if (newNode) {
        newNode.position = {
          x: pendingNodePosition.value.x,
          y: pendingNodePosition.value.y
        }
      }
      pendingNodePosition.value = null
    }

    // Auto-connect start node to new node
    if (formGeneratorMode.value === 'create' && nextNodeId) {
      await autoAddStartEdge(nextNodeId)
    }

    await saveVueFlowGraph()
  } catch (error) {
    console.error('Error refreshing workflow after dynamic form submission:', error)
  } finally {
    closeDynamicFormGenerator()
  }
}

const handleFormGeneratorCopy = (payload) => {
  try {
    const copied = payload?.initialFormData ? cloneDeep(payload.initialFormData) : null
    if (copied && typeof copied === 'object') {
      copied.id = ''
    }

    // @close of original modal calls closeDynamicFormGenerator()
    // Defer new "create node" modal to the next tick to avoid being closed
    setTimeout(() => {
      openDynamicFormGenerator('node', {
        mode: 'create',
        initialFormData: copied
      })
    }, 0)
  } catch (error) {
    console.error('Error copying node:', error)
  }
}

const openNodeEditor = (nodeId) => {
  if (!nodeId) {
    return
  }
  const yamlNodeContent = (yamlContent.value?.graph?.nodes || []).find(node => node.id === nodeId)
  if (!yamlNodeContent) {
    console.warn(`[WorkflowView] Node with id "${nodeId}" not found for editing`)
    return
  }
  // Pass YAML without specific node to the FormGenerator to "recreate" the node
  const sanitizedYaml = buildYamlWithoutNode(nodeId)
  openDynamicFormGenerator('node', {
    initialYaml: sanitizedYaml,
    initialFormData: yamlNodeContent,
    mode: 'edit'
  })
}

const openEdgeEditor = (fromId, toId, fallbackData = null) => {
  if (!fromId || !toId) {
    return
  }
  const yamlEdge = (yamlContent.value?.graph?.edges || []).find(edge => edge.from === fromId && edge.to === toId)
  const edgeData = yamlEdge || (fallbackData ? cloneDeep(fallbackData) : null)
  if (!edgeData) {
    console.warn(`[WorkflowView] Edge "${fromId}-${toId}" not found for editing`)
    return
  }
  const sanitizedYaml = buildYamlWithoutEdge(fromId, toId)
  openDynamicFormGenerator('edge', {
    initialYaml: sanitizedYaml,
    initialFormData: edgeData,
    mode: 'edit'
  })
}

// Create Node functions
const openCreateNodeModal = () => {
  // Set position to center of graph when creating from 'Create Node'
  pendingNodePosition.value = getCentralPosition()
  openDynamicFormGenerator('node', { mode: 'create' })
}

const openManageVarsModal = () => {
  const currentVars = yamlContent.value?.vars || null
  const sanitizedYaml = buildYamlWithoutVars()
  openDynamicFormGenerator('vars', {
    recursive: false,
    initialYaml: sanitizedYaml,
    initialFormData: currentVars ? { vars: currentVars } : null,
    mode: currentVars ? 'edit' : 'create',
    fieldFilter: ['vars']
  })
}

const openManageMemoriesModal = () => {
  const currentMemories = yamlContent.value?.graph?.memory || null
  const sanitizedYaml = buildYamlWithoutMemory()
  openDynamicFormGenerator('memory', {
    initialYaml: sanitizedYaml,
    initialFormData: currentMemories ? { memory: currentMemories } : null,
    mode: currentMemories ? 'edit' : 'create',
    fieldFilter: ['memory']
  })
}

const openConfigureGraphModal = () => {
  const currentGraph = yamlContent.value?.graph || null
  const sanitizedYaml = buildYamlWithoutGraph()
  openDynamicFormGenerator('graph', {
    recursive: false,
    initialYaml: sanitizedYaml,
    initialFormData: currentGraph,
    mode: currentGraph ? 'edit' : 'create',
    fieldFilter: null,
    readOnlyFields: ['id']
  })
}

const onNodeClick = (event) => {
  if (isCreatingConnection.value) {
    return
  }
  const clickedNode = event?.node || event
  if (!clickedNode?.id) {
    return
  }

  // Ignore left click for start node but show dim animation
  if (clickedNode.id === START_NODE_ID) {
    dimStartNode()
    return
  }

  openNodeEditor(clickedNode.id)
}

const onEdgeClick = (event) => {
  const clickedEdge = event?.edge || event
  if (!clickedEdge?.id) {
    return
  }

  const fromId = clickedEdge.data?.from || clickedEdge.source || ''
  const toId = clickedEdge.data?.to || clickedEdge.target || ''

  // Ignore start node edge
  if (fromId === START_NODE_ID || toId === START_NODE_ID) {
    return
  }
  if (!fromId || !toId) {
    return
  }

  const fallbackData = {
    from: fromId,
    to: toId
  }

  if (clickedEdge.data?.condition !== undefined) {
    fallbackData.condition = clickedEdge.data.condition
  }

  if (clickedEdge.data?.trigger !== undefined) {
    fallbackData.trigger = clickedEdge.data.trigger
  }

  openEdgeEditor(fromId, toId, fallbackData)
}

// Autosave when moving nodes
const onNodeDragStop = () => {
  saveVueFlowGraph()
}

const onConnect = async (connection) => {
  if (!connection?.source || !connection?.target) {
    return
  }

  // Set flag to avoid opening node edit modal
  isCreatingConnection.value = true

  // Special handling for StartNode connections
  if (connection.source === START_NODE_ID) {
    // Add target node to graph.start array instead of opening FormGenerator
    const source = yamlContent.value
    if (!source?.graph) {
      setTimeout(() => {
        isCreatingConnection.value = false
      }, 10)
      return
    }
    const sourceGraph = source.graph

    // Ensure graph.start exists as an array
    const currentStart = Array.isArray(sourceGraph.start) ? sourceGraph.start : []

    // Add target node to start array if not already present
    if (!currentStart.includes(connection.target)) {
      const nextSnapshot = {
        ...source,
        graph: {
          ...sourceGraph,
          start: [...currentStart, connection.target]
        }
      }

      // Persist the updated YAML
      const ok = await persistYamlSnapshot(nextSnapshot)
      if (ok) {
        await loadYamlFile()
        syncVueNodesAndEdgesData()
        await nextTick()
        await saveVueFlowGraph()
      }
    }

    setTimeout(() => {
      isCreatingConnection.value = false
    }, 10)
    return
  }

  // Do not open modal if edge already exists
  const yamlEdges = yamlContent.value?.graph?.edges || []
  const edgeAlreadyExistsInYaml = yamlEdges.some(
    e => e.from === connection.source && e.to === connection.target
  )
  const edgeAlreadyExistsInGraph = edges.value.some(
    e => e.source === connection.source && e.target === connection.target
  )
  if (edgeAlreadyExistsInYaml || edgeAlreadyExistsInGraph) {
    setTimeout(() => {
      isCreatingConnection.value = false
    }, 10)
    return
  }

  // Remove the automatically created edge (VueFlow may optimistically add one)
  const autoCreatedEdgeIndex = edges.value.findIndex(
    edge => edge.source === connection.source && edge.target === connection.target
  )
  if (autoCreatedEdgeIndex !== -1) {
    edges.value.splice(autoCreatedEdgeIndex, 1)
  }

  openDynamicFormGenerator('edge', {
    initialFormData: {
      from: connection.source,
      to: connection.target,
      condition: {
        type: 'function',
        config: {
          name: 'true'
        }
      },
      trigger: true
    },
    mode: 'create'
  })

  // Reset flag after a short delay so click handlers stay disabled
  setTimeout(() => {
    isCreatingConnection.value = false
  }, 100)
}

// Create Edge functions
const openCreateEdgeModal = () => {
  openDynamicFormGenerator('edge', { mode: 'create' })
}

const goToLaunch = () => {
  if (!workflowName.value) {
    return
  }
  const fileName = workflowName.value.endsWith('.yaml')
    ? workflowName.value
    : `${workflowName.value}.yaml`

  const resolved = router.resolve({
    path: '/launch',
    query: { workflow: fileName }
  })

  window.open(resolved.href, '_blank', 'noopener')
}

// Modal functions for rename and copy workflow
const openRenameWorkflowModal = () => {
  showMenu.value = false
  renameWorkflowName.value = workflowName.value.replace('.yaml', '')
  showRenameModal.value = true
}

const closeRenameModal = () => {
  showRenameModal.value = false
  renameWorkflowName.value = ''
}

const handleRenameSubmit = async () => {
  if (!renameWorkflowName.value.trim()) {
    return
  }

  const newName = renameWorkflowName.value.trim()
  const result = await postYamlNameChange(workflowName.value, newName)

  if (result.success) {
    // Handle VueGraph rename
    const oldWorkflowKey = workflowName.value.replace('.yaml', '')
    const newWorkflowKey = newName

    // Save VueGraph into new workflow
    try {
      const oldVueGraphResult = await fetchVueGraph(oldWorkflowKey)
      if (oldVueGraphResult.success && oldVueGraphResult.content) {
        const saveResult = await postVuegraphs({
          filename: newWorkflowKey,
          content: oldVueGraphResult.content
        })
        if (!saveResult.success) {
          console.warn('Failed to rename VueGraph:', saveResult.message)
        }
      }
    } catch (error) {
      console.warn('Error handling VueGraph rename:', error)
    }

    alert(result.message)
    closeRenameModal()

    // Refresh workflow list first
    emit('refresh-workflows')

    // Small delay to allow workflow list to refresh before navigating
    await new Promise(resolve => setTimeout(resolve, 500))

    // Navigate to the renamed workflow
    const newWorkflowName = result.filename || `${newName}.yaml`
    const workflowNameWithoutExtension = newWorkflowName.replace('.yaml', '')
    router.push({ path: `/workflows/${workflowNameWithoutExtension}` })
  } else {
    alert(result.error?.message || 'Failed to rename workflow')
  }
}

const openCopyWorkflowModal = () => {
  showMenu.value = false
  copyWorkflowName.value = workflowName.value.replace('.yaml', '') + '_copy'
  showCopyModal.value = true
}

const closeCopyModal = () => {
  showCopyModal.value = false
  copyWorkflowName.value = ''
}

const handleCopySubmit = async () => {
  if (!copyWorkflowName.value.trim()) {
    return
  }

  const newName = copyWorkflowName.value.trim()
  const result = await postYamlCopy(workflowName.value, newName)

  if (result.success) {
    // Handle VueGraph copy
    const sourceWorkflowKey = workflowName.value.replace('.yaml', '')
    const targetWorkflowKey = newName

    try {
      // Load the VueGraph for the source workflow
      const sourceVueGraphResult = await fetchVueGraph(sourceWorkflowKey)
      if (sourceVueGraphResult.success && sourceVueGraphResult.content) {
        // Save the VueGraph with the new workflow name
        const saveResult = await postVuegraphs({
          filename: targetWorkflowKey,
          content: sourceVueGraphResult.content
        })
        if (!saveResult.success) {
          console.warn('Failed to copy VueGraph:', saveResult.message)
        }
      }
    } catch (error) {
      console.warn('Error handling VueGraph copy:', error)
    }

    alert(result.message)
    closeCopyModal()

    // Refresh workflow list first
    emit('refresh-workflows')

    // Small delay to allow workflow list to refresh before navigating
    await new Promise(resolve => setTimeout(resolve, 500))

    // Navigate to the copied workflow
    const newWorkflowName = result.filename || `${newName}.yaml`
      const workflowNameWithoutExtension = newWorkflowName.replace('.yaml', '')
    router.push({ path: `/workflows/${workflowNameWithoutExtension}` })
  } else {
    alert(result.message || result.error?.message || 'Failed to copy workflow')
  }
}
</script>

<style scoped>
.workflow-view {
  width: 100%;
  height: calc(100vh - 55px);
  display: flex;
  flex-direction: column;
  background-color: #1a1a1a;
  color: #f2f2f2;
  font-family: 'Inter', sans-serif;
  position: relative;
  overflow: hidden;
}

.workflow-bg {
  position: fixed;
  top: -150px;
  left: 0;
  right: 0;
  height: 500px;
  background: linear-gradient(
    90deg,
    #aaffcd,
    #99eaf9,
    #a0c4ff
  );
  filter: blur(120px);
  opacity: 0.15;
  z-index: 0;
  pointer-events: none;
}

.content {
  position: relative;
  z-index: 1;
}

.header,
.tabs {
  position: relative;
  z-index: 2;
}

.header {
  display: flex;
  align-items: center;
  padding: 0 20px;
  height: 40px;
  background-color: rgba(255, 255, 255, 0.05);
  border-bottom: 1px solid rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(5px);
  flex-shrink: 0;
}

.back-button {
  padding: 8px;
  margin-right: 16px;
  background: transparent;
  color: rgba(255, 255, 255, 0.6);
  cursor: pointer;
  transition: color 0.2s ease;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid transparent;
  outline: none;
}

.back-button:hover {
  background: transparent;
  color: #f2f2f2;
  border-color: transparent;
}

.back-button:focus,
.back-button:focus-visible {
  outline: none;
  border-color: transparent;
}

.workflow-name {
  color: #f2f2f2;
  font-size: 18px;
  font-weight: 600;
  margin: 0;
}

.tabs {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0 20px;
  height: 50px;
  background-color: rgba(255, 255, 255, 0.02);
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(5px);
  flex-shrink: 0;
  position: sticky;
  bottom: 0;
  z-index: 2;
}

.tab-buttons {
  display: flex;
  gap: 4px;
  height: 100%;
}

.tab {
  padding: 0 20px;
  height: 100%;
  border: none;
  background: transparent;
  cursor: pointer;
  font-size: 14px;
  font-family: 'Inter', sans-serif;
  color: #8e8e8e;
  font-weight: 500;
  transition: color 0.2s ease;
  position: relative;
}

.tab:hover {
  color: #f2f2f2;
}

.tab.active {
  background: linear-gradient(
    135deg,
    #aaffcd,
    #99eaf9,
    #a0c4ff
  );
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  font-weight: 500;
}

.editor-actions {
  display: flex;
  gap: 12px;
  align-items: center;
  height: 100%;
}

/* Glass Button - Matching WorkflowList entries */
  .glass-button {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  background-color: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  cursor: pointer;
  transition: all 0.3s ease;
  color: #f2f2f2;
  font-size: 14px;
  position: relative;
  z-index: 1;
  backdrop-filter: blur(5px);
}

.launch-button-primary {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  border: none;
  border-radius: 12px;
  cursor: pointer;
  transition: all 0.3s ease;
  color: #1a1a1a;
  font-size: 14px;
  font-weight: 600;
  background: linear-gradient(
    135deg,
    #aaffcd,
    #99eaf9,
    #a0c4ff
  );
  background-size: 200% 100%;
  animation: gradientShift 6s ease-in-out infinite;
  backdrop-filter: blur(5px);
  position: relative;
  z-index: 1;
}

.launch-button-primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
  opacity: 0.9;
}

.glass-button:hover {
  background-color: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.05);
}

@keyframes gradientShift {
  0%, 100% { background-position: 0% 0%; }
  50% { background-position: 100% 0%; }
}

.glass-button::before {
  content: '';
  position: absolute;
  inset: -2px;
  z-index: -1;
  border-radius: 14px;
  padding: 2px;
  background: linear-gradient(
    135deg,
    #aaffcd,
    #99eaf9,
    #a0c4ff
  );
  -webkit-mask: 
     linear-gradient(#f2f2f2 0 0) content-box, 
     linear-gradient(#f2f2f2 0 0);
  mask: 
     linear-gradient(#f2f2f2 0 0) content-box, 
     linear-gradient(#f2f2f2 0 0);
  -webkit-mask-composite: xor;
  mask-composite: exclude;
  opacity: 0;
  transition: opacity 0.3s ease;
  background-size: 200% 100%;
  animation: gradientShift 6s ease-in-out infinite;
  filter: blur(4px);
}

.glass-button:hover::before {
  opacity: 1;
}

.btn-icon {
  font-size: 16px;
}

/* Menu Dropdown */
.menu-container {
  position: relative;
  z-index: 3;
  height: 100%;
}

.menu-trigger {
  width: 40px;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  cursor: pointer;
  transition: all 0.2s ease;
  color: rgba(255, 255, 255, 0.7);
}

.menu-dropdown {
  position: absolute;
  bottom: 100%;
  right: 0;
  background: rgba(60, 60, 60, 0.99);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  padding: 8px;
  min-width: 180px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
  backdrop-filter: blur(10px);
  z-index: 3;
}

.menu-item {
  padding: 10px 16px;
  color: rgba(255, 255, 255, 0.8);
  font-size: 14px;
  cursor: pointer;
  border-radius: 8px;
  transition: all 0.2s ease;
}

.menu-item:hover {
  background-color: rgba(255, 255, 255, 0.1);
  color: #f2f2f2;
}

/* Transitions */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.2s ease, transform 0.2s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
  transform: translateY(-10px);
}

.content {
  flex: 1;
  overflow: hidden;
  position: relative;
  min-height: 0;
}

.yaml-editor {
  height: 100%;
  display: flex;
  flex-direction: column;
}

.yaml-error {
  padding: 12px 20px;
  background-color: rgba(255, 68, 68, 0.1);
  border-bottom: 1px solid rgba(255, 68, 68, 0.3);
  color: #ff8888;
  font-size: 14px;
  margin: 0;
}

.yaml-textarea {
  flex: 1;
  padding: 20px;
  border: none;
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
  font-size: 14px;
  line-height: 1.5;
  resize: none;
  outline: none;
  background: transparent;
  color: #d4d4d4;
}

.yaml-textarea::-webkit-scrollbar {
  display: none;
}

.yaml-error-border {
  border: 2px solid #ff4444 !important;
}

.vueflow-container {
  height: 100%;
  width: 100%;
  background-color: rgba(255, 255, 255, 0.03);
  backdrop-filter: blur(5px);
  z-index: 1;
  position: relative;
}

.vueflow-graph {
  width: 100%;
  height: 100%;
}

.context-menu {
  position: absolute;
  min-width: 160px;
  background: rgba(40, 40, 40, 0.98);
  border-radius: 10px;
  padding: 6px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.6);
  border: 1px solid rgba(255, 255, 255, 0.1);
  backdrop-filter: blur(10px);
  z-index: 5;
}

.context-menu-item {
  padding: 8px 12px;
  color: rgba(255, 255, 255, 0.85);
  font-size: 13px;
  border-radius: 8px;
  cursor: pointer;
  transition: background-color 0.15s ease, color 0.15s ease;
}

.context-menu-item:hover {
  background-color: rgba(255, 255, 255, 0.1);
  color: #f2f2f2;
}

/* Modal Styles - Matching FormGenerator modal-content */
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
  backdrop-filter: blur(4px);
}

.modal-content {
  display: flex;
  flex-direction: column;
  width: 90%;
  max-width: 500px;
  max-height: 90vh;
  background-color: rgba(33, 33, 33, 0.92);
  border-radius: 16px;
  border: 1px solid rgba(255, 255, 255, 0.08);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6);
  overflow: hidden;
  backdrop-filter: blur(10px);
}

.modal-header {
  flex-shrink: 0;
  height: 28px;
  display: flex;
  align-items: center;
  padding: 0 20px;
  background-color: transparent;
  border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}

.modal-title {
  color: #f2f2f2;
  font-size: 15px;
  font-weight: 600;
  margin: 0;
  flex: 1;
}

.close-button {
  margin-left: auto;
  background: transparent;
  border: none;
  width: 18px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: rgba(255, 255, 255, 0.6);
  transition: color 0.2s ease;
}

.close-button:hover {
  color: #f2f2f2;
}

.modal-body {
  flex: 1;
  padding: 20px;
  max-height: none;
  overflow-y: auto;
  border-top: 1px solid rgba(255, 255, 255, 0.04);
  border-bottom: 1px solid rgba(255, 255, 255, 0.04);
  scrollbar-width: none;
}

.modal-body::-webkit-scrollbar {
  display: none;
}

.form-group {
  margin-bottom: 16px;
}

.form-label {
  display: block;
  color: #f2f2f2;
  font-size: 13px;
  font-weight: 500;
  margin-bottom: 8px;
}

.form-input {
  width: 90%;
  padding: 12px 16px;
  background-color: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  color: #f2f2f2;
  font-size: 14px;
  font-family: 'Inter', sans-serif;
  outline: none;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}

.form-input:focus {
  border-color: rgba(170, 255, 205, 0.5);
  background-color: rgba(255, 255, 255, 0.08);
}

.form-input::placeholder {
  color: rgba(255, 255, 255, 0.4);
}

.modal-footer {
  flex-shrink: 0;
  display: flex;
  justify-content: flex-end;
  gap: 12px;
  padding: 16px 20px;
  background-color: transparent;
  border-top: 1px solid rgba(255, 255, 255, 0.04);
}

.cancel-button,
.submit-button {
  padding: 8px 16px;
  border-radius: 8px;
  font-size: 14px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.2s ease;
  font-family: 'Inter', sans-serif;
}

.cancel-button {
  background-color: transparent;
  border: 1px solid rgba(255, 255, 255, 0.1);
  color: rgba(255, 255, 255, 0.7);
}

.cancel-button:hover {
  background-color: rgba(255, 255, 255, 0.05);
  border-color: rgba(255, 255, 255, 0.2);
  color: #f2f2f2;
}

.submit-button {
  background: linear-gradient(135deg, #aaffcd, #99eaf9, #a0c4ff);
  border: none;
  color: #1a1a1a;
  font-weight: 600;
}

.submit-button:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}

.submit-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none;
}
</style>
