<template>
  <div class="workflow-editor">
    <div class="wf-toolbar">
      <div class="wf-toolbar-left">
        <el-input v-model="workflow.name" size="small" class="wf-name-input" placeholder="流程名称" />
        <el-tag size="small" type="info">流程库 {{ library.workflows.length }} 个</el-tag>
      </div>
      <div class="wf-toolbar-center">
        <el-tag v-for="(issue, i) in validationIssues" :key="i" size="small" :type="issue.level === 'error' ? 'danger' : 'warning'" class="wf-issue-tag">
          {{ issue.message }}
        </el-tag>
        <el-tag v-if="validationIssues.length === 0" size="small" type="success">校验通过</el-tag>
      </div>
      <div class="wf-toolbar-right">
        <el-button size="small" type="danger" plain :disabled="!hasDeletableSelection" @click="deleteSelection">
          删除选中
        </el-button>
        <el-button size="small" @click="resetDemo">加载示例</el-button>
        <el-button size="small" @click="onImportJson">导入</el-button>
        <el-button size="small" type="primary" plain @click="onExportAll">导出</el-button>
        <el-button size="small" type="danger" plain @click="onClearCanvas">清除画布</el-button>
      </div>
    </div>

    <div class="wf-body">
      <aside class="wf-panel wf-panel-left">
        <WorkflowListPanel
          :workflows="library.workflows"
          :active-id="library.activeWorkflowId"
          @select="switchToWorkflow"
          @create="onCreateWorkflow"
          @save="onSave"
          @duplicate="onDuplicateWorkflow"
          @delete="onDeleteWorkflow"
        />
        <div class="wf-panel-title">控件库</div>
        <div class="wf-palette-body">
          <NodePalette />
        </div>
      </aside>

      <main class="wf-panel wf-panel-center">
        <VueFlow
          v-model:nodes="flowNodes"
          v-model:edges="flowEdges"
          :node-types="nodeTypes"
          :default-viewport="workflow.viewport ?? { x: 0, y: 0, zoom: 0.85 }"
          :delete-key-code="['Delete', 'Backspace']"
          fit-view-on-init
          @drop="onDrop"
          @dragover="onDragOver"
          @node-click="onNodeClick"
          @edge-click="onEdgeClick"
          @pane-click="onPaneClick"
          @connect="onConnect"
          @nodes-change="onNodesChange"
          @edges-change="onEdgesChange"
        >
          <Background pattern-color="#e8eaed" :gap="16" />
          <Controls position="bottom-right" />
        </VueFlow>
      </main>

      <aside class="wf-panel wf-panel-right">
        <div class="wf-panel-title">参数配置</div>
        <EdgeInspector v-if="selectedEdge" :edge="selectedEdge" @delete="deleteSelectedEdge" />
        <NodeInspector
          v-else
          :node="selectedNode"
          @update="onInspectorUpdate"
          @delete="confirmDeleteSelectedNode"
        />
      </aside>
    </div>
  </div>
</template>

<script setup lang="ts">
import { computed, markRaw, nextTick, reactive, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import {
  VueFlow,
  type Connection,
  type EdgeChange,
  type GraphEdge,
  type GraphNode,
  type NodeChange,
  type NodeTypesObject,
  useVueFlow
} 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/core/dist/theme-default.css'
import '@vue-flow/controls/dist/style.css'

import NodePalette from './NodePalette.vue'
import NodeInspector from './NodeInspector.vue'
import EdgeInspector from './EdgeInspector.vue'
import WorkflowNode from './WorkflowNode.vue'
import WorkflowListPanel from './WorkflowListPanel.vue'
import type { WorkflowDefinition, WorkflowNodeDef } from '@/types/workflow'
import { NODE_CATALOG_MAP, defaultParamsForType } from '@/workflow/nodeCatalog'
import {
  addWorkflow,
  createEmptyWorkflow,
  duplicateWorkflow,
  createInitialWorkflowLibrary,
  ensureDemoInLibrary,
  getActiveWorkflow,
  getWorkflowById,
  mergeWorkflowsIntoLibrary,
  removeWorkflow,
  setActiveWorkflowId,
  upsertWorkflow,
  type StoredWorkflowLibrary
} from '@/workflow/workflowLibrary'
import {
  importResultSummary,
  parseWorkflowJsonText,
  pickJsonFile,
  prepareImportedWorkflow,
  readFileAsText,
  resolveWorkflowIdConflicts,
  WorkflowImportError
} from '@/workflow/workflowImport'
import {
  cloneWorkflowDefinition,
  downloadWorkflowCatalogExport,
  fromFlowState,
  nextEdgeId,
  nextNodeId,
  normalizeNodeParams,
  toFlowEdges,
  toFlowNodes,
  validateWorkflow
} from '@/workflow/workflowUtils'

const nodeTypes = { workflow: markRaw(WorkflowNode) } as NodeTypesObject

const library = ref<StoredWorkflowLibrary>(createInitialWorkflowLibrary())
const workflow = reactive<WorkflowDefinition>(cloneWorkflowDefinition(getActiveWorkflow(library.value)))
const flowNodes = ref(toFlowNodes(workflow.nodes))
const flowEdges = ref(toFlowEdges(workflow.edges))
const selectedNodeId = ref<string | null>(null)
const selectedEdgeId = ref<string | null>(null)

let saveTimer: ReturnType<typeof setTimeout> | null = null

const { screenToFlowCoordinate, addEdges, getViewport, setViewport, removeNodes, removeEdges, getSelectedNodes, getSelectedEdges } =
  useVueFlow()

const selectedNode = computed<WorkflowNodeDef | null>(() => {
  if (!selectedNodeId.value) return null
  return workflow.nodes.find((n) => n.id === selectedNodeId.value) ?? null
})

const selectedEdge = computed(() => {
  if (!selectedEdgeId.value) return null
  const edge = flowEdges.value.find((e) => e.id === selectedEdgeId.value)
  if (!edge) return null
  const sourceNode = workflow.nodes.find((n) => n.id === edge.source)
  const targetNode = workflow.nodes.find((n) => n.id === edge.target)
  return {
    id: edge.id,
    sourceLabel: sourceNode?.label ?? edge.source,
    targetLabel: targetNode?.label ?? edge.target,
    label: edge.label
  }
})

const hasDeletableSelection = computed(() => {
  return (
    getSelectedNodes.value.length > 0 ||
    getSelectedEdges.value.length > 0 ||
    selectedNodeId.value !== null ||
    selectedEdgeId.value !== null
  )
})

const validationIssues = computed(() => validateWorkflow(workflow))

watch([flowNodes, flowEdges], () => {
  syncWorkflowFromFlow()
  scheduleSyncToLibrary()
}, { deep: true })

watch(() => workflow.name, scheduleSyncToLibrary)

function syncWorkflowFromFlow() {
  const { nodes, edges } = fromFlowState(flowNodes.value, flowEdges.value)
  workflow.nodes = nodes
  workflow.edges = edges
}

function buildSnapshot(): WorkflowDefinition {
  syncWorkflowFromFlow()
  const vp = getViewport()
  return {
    ...cloneWorkflowDefinition(workflow),
    version: workflow.version,
    viewport: { x: vp.x, y: vp.y, zoom: vp.zoom }
  }
}

/** 将当前画布同步到内存流程库（不写浏览器存储） */
function syncCurrentToLibrary(): WorkflowDefinition {
  const snapshot = buildSnapshot()
  library.value = upsertWorkflow(library.value, snapshot)
  return snapshot
}

function scheduleSyncToLibrary() {
  if (saveTimer) clearTimeout(saveTimer)
  saveTimer = setTimeout(() => {
    syncCurrentToLibrary()
  }, 400)
}

function applyWorkflowToCanvas(def: WorkflowDefinition) {
  const copy = cloneWorkflowDefinition(def)
  Object.assign(workflow, copy)
  flowNodes.value = toFlowNodes(workflow.nodes)
  flowEdges.value = toFlowEdges(workflow.edges)
  selectedNodeId.value = null
  selectedEdgeId.value = null
  void nextTick(() => {
    const vp = copy.viewport ?? { x: 0, y: 0, zoom: 0.85 }
    setViewport(vp)
  })
}

function switchToWorkflow(id: string) {
  if (id === library.value.activeWorkflowId) return
  syncCurrentToLibrary()
  const next = setActiveWorkflowId(library.value, id)
  if (!next) return
  library.value = next
  applyWorkflowToCanvas(getActiveWorkflow(library.value))
}

function onCreateWorkflow() {
  syncCurrentToLibrary()
  const created = createEmptyWorkflow()
  library.value = addWorkflow(library.value, created)
  applyWorkflowToCanvas(created)
  ElMessage.success('已创建新流程')
}

async function onDeleteWorkflow(id: string) {
  const target = library.value.workflows.find((w) => w.id === id)
  if (!target) return
  try {
    await ElMessageBox.confirm(`确定从流程库删除「${target.name}」？`, '删除流程', {
      type: 'warning',
      confirmButtonText: '删除',
      cancelButtonText: '取消',
      confirmButtonClass: 'el-button--danger'
    })
  } catch {
    return
  }
  if (id === library.value.activeWorkflowId) {
    syncCurrentToLibrary()
  }
  const wasActive = id === library.value.activeWorkflowId
  const next = removeWorkflow(library.value, id)
  if (!next) {
    ElMessage.warning('至少保留一个流程')
    return
  }
  library.value = next
  if (wasActive) {
    applyWorkflowToCanvas(getActiveWorkflow(library.value))
  }
  ElMessage.success('流程已删除')
}

function onDuplicateWorkflow(id: string) {
  syncCurrentToLibrary()
  const next = duplicateWorkflow(library.value, id)
  if (!next) return
  library.value = next
  applyWorkflowToCanvas(getActiveWorkflow(library.value))
  ElMessage.success('已复制流程')
}

function onSave() {
  const snapshot = syncCurrentToLibrary()
  const errors = validateWorkflow(snapshot).filter((i) => i.level === 'error')
  if (errors.length > 0) {
    ElMessage.error(errors[0]!.message)
    return
  }
  const warnings = validateWorkflow(snapshot).filter((i) => i.level === 'warning')
  if (warnings.length > 0) {
    ElMessage.warning(`已保存「${snapshot.name || '未命名流程'}」，但有 ${warnings.length} 条校验警告`)
  } else {
    ElMessage.success(`已保存「${snapshot.name || '未命名流程'}」`)
  }
}

function onExportAll() {
  syncCurrentToLibrary()
  const workflows = library.value.workflows
  const errorWorkflows = workflows.filter((w) => validateWorkflow(w).some((i) => i.level === 'error'))
  if (errorWorkflows.length > 0) {
    ElMessage.error(`「${errorWorkflows[0]!.name}」存在校验错误，请先修复后再导出`)
    return
  }
  downloadWorkflowCatalogExport(workflows, 'workflow-catalog')
  ElMessage.success(`已导出 ${workflows.length} 个流程到 workflow-catalog.json`)
}

async function onImportJson() {
  const file = await pickJsonFile()
  if (!file) return

  let text: string
  try {
    text = await readFileAsText(file)
  } catch {
    ElMessage.error('读取文件失败')
    return
  }

  let parsed
  try {
    parsed = parseWorkflowJsonText(text)
  } catch (e) {
    ElMessage.error(e instanceof WorkflowImportError ? e.message : '导入失败')
    return
  }

  const summary = importResultSummary(parsed)
  try {
    await ElMessageBox.confirm(
      `${summary}。将追加到流程库（流程 ID 与已有重复时自动分配新 ID），是否继续？`,
      '导入 JSON',
      { type: 'info', confirmButtonText: '导入', cancelButtonText: '取消' }
    )
  } catch {
    return
  }

  syncCurrentToLibrary()
  const existingIds = new Set(library.value.workflows.map((w) => w.id))
  const prepared = resolveWorkflowIdConflicts(
    parsed.workflows.map((w) => prepareImportedWorkflow(w)),
    existingIds
  )

  const merged = mergeWorkflowsIntoLibrary(library.value, prepared)
  if (!merged) {
    ElMessage.error('没有可导入的流程')
    return
  }

  library.value = merged.library

  const active = getWorkflowById(library.value, merged.lastImportedId)
  if (active) {
    applyWorkflowToCanvas(active)
  }

  const warnCount = prepared.reduce(
    (sum, w) =>
      sum + validateWorkflow(w).filter((i) => i.level === 'warning').length,
    0
  )
  if (warnCount > 0) {
    ElMessage.warning(`已导入 ${prepared.length} 个流程，共 ${warnCount} 条校验警告，请在顶栏查看`)
  } else {
    ElMessage.success(
      prepared.length === 1
        ? `已导入流程「${prepared[0]!.name}」`
        : `已导入 ${prepared.length} 个流程`
    )
  }
}

function onDragOver(e: DragEvent) {
  e.preventDefault()
  if (e.dataTransfer) e.dataTransfer.dropEffect = 'move'
}

function onDrop(e: DragEvent) {
  const type = e.dataTransfer?.getData('application/workflow-node')
  if (!type) return

  const def = NODE_CATALOG_MAP.get(type)
  if (!def) return

  if (def.singleton && workflow.nodes.some((n) => n.type === type)) {
    ElMessage.warning(`「${def.label}」节点在流程中只能有一个`)
    return
  }

  const pos = screenToFlowCoordinate({ x: e.clientX, y: e.clientY })
  const id = nextNodeId()
  flowNodes.value = [
    ...flowNodes.value,
    {
      id,
      type: 'workflow',
      position: pos,
      data: {
        type,
        label: def.label,
        params: normalizeNodeParams(type, defaultParamsForType(type))
      }
    }
  ]
}

function onNodeClick({ node }: { node: { id: string } }) {
  selectedNodeId.value = node.id
  selectedEdgeId.value = null
}

function onEdgeClick({ edge }: { edge: { id: string } }) {
  selectedEdgeId.value = edge.id
  selectedNodeId.value = null
}

function onPaneClick() {
  selectedNodeId.value = null
  selectedEdgeId.value = null
}

function onConnect(conn: Connection) {
  if (!conn.source || !conn.target) return
  addEdges([
    {
      id: nextEdgeId(),
      source: conn.source,
      target: conn.target,
      sourceHandle: conn.sourceHandle ?? undefined,
      animated: true,
      style: { stroke: '#409eff', strokeWidth: 2 }
    }
  ])
}

function onNodesChange(changes: NodeChange[]) {
  for (const change of changes) {
    if (change.type === 'remove' && change.id === selectedNodeId.value) {
      selectedNodeId.value = null
    }
  }
  syncWorkflowFromFlow()
}

function onEdgesChange(changes: EdgeChange[]) {
  for (const change of changes) {
    if (change.type === 'remove' && change.id === selectedEdgeId.value) {
      selectedEdgeId.value = null
    }
  }
  syncWorkflowFromFlow()
}

function collectNodesToDelete(): GraphNode[] {
  const selected = getSelectedNodes.value
  if (selected.length > 0) return selected
  if (!selectedNodeId.value) return []
  const node = flowNodes.value.find((n) => n.id === selectedNodeId.value)
  return node ? [node as GraphNode] : []
}

function collectEdgesToDelete(): GraphEdge[] {
  const selected = getSelectedEdges.value
  if (selected.length > 0) return selected
  if (!selectedEdgeId.value) return []
  const edge = flowEdges.value.find((e) => e.id === selectedEdgeId.value)
  return edge ? [edge as GraphEdge] : []
}

async function confirmDeleteSelectedNode() {
  const nodes = collectNodesToDelete()
  if (nodes.length === 0) return
  try {
    await ElMessageBox.confirm(
      nodes.length === 1
        ? `确定删除节点「${nodes[0]!.data.label}」？关联连线将一并移除。`
        : `确定删除选中的 ${nodes.length} 个节点？关联连线将一并移除。`,
      '删除节点',
      {
        type: 'warning',
        confirmButtonText: '删除',
        cancelButtonText: '取消',
        confirmButtonClass: 'el-button--danger'
      }
    )
  } catch {
    return
  }
  removeNodes(nodes)
  selectedNodeId.value = null
  ElMessage.success(nodes.length === 1 ? '节点已删除' : `已删除 ${nodes.length} 个节点`)
}

function deleteSelectedEdge() {
  const edges = collectEdgesToDelete()
  if (edges.length === 0) return
  removeEdges(edges)
  selectedEdgeId.value = null
  ElMessage.success(edges.length === 1 ? '连线已删除' : `已删除 ${edges.length} 条连线`)
}

async function deleteSelection() {
  const nodes = collectNodesToDelete()
  let edgesToDelete = collectEdgesToDelete()
  if (nodes.length === 0 && edgesToDelete.length === 0) {
    ElMessage.info('请先选中要删除的节点或连线')
    return
  }

  if (nodes.length > 0) {
    try {
      await ElMessageBox.confirm(
        nodes.length === 1
          ? `确定删除节点「${nodes[0]!.data.label}」？关联连线将一并移除。`
          : `确定删除选中的 ${nodes.length} 个节点？关联连线将一并移除。`,
        '删除节点',
        {
          type: 'warning',
          confirmButtonText: '删除',
          cancelButtonText: '取消',
          confirmButtonClass: 'el-button--danger'
        }
      )
    } catch {
      return
    }
    removeNodes(nodes)
    selectedNodeId.value = null
    const removedNodeIds = new Set(nodes.map((n) => n.id))
    edgesToDelete = edgesToDelete.filter((e) => !removedNodeIds.has(e.source) && !removedNodeIds.has(e.target))
  }

  edgesToDelete = edgesToDelete.filter((e) => flowEdges.value.some((fe) => fe.id === e.id))
  if (edgesToDelete.length > 0) {
    removeEdges(edgesToDelete)
    selectedEdgeId.value = null
  }

  const parts: string[] = []
  if (nodes.length > 0) parts.push(`${nodes.length} 个节点`)
  if (edgesToDelete.length > 0) parts.push(`${edgesToDelete.length} 条连线`)
  ElMessage.success(`已删除 ${parts.join('、')}`)
}

function onInspectorUpdate(patch: Partial<WorkflowNodeDef>) {
  if (!selectedNodeId.value) return
  const idx = flowNodes.value.findIndex((n) => n.id === selectedNodeId.value)
  if (idx < 0) return

  const node = flowNodes.value[idx]
  if (patch.label !== undefined) node.data.label = patch.label
  if (patch.params !== undefined) node.data.params = { ...patch.params }
  flowNodes.value = [...flowNodes.value]
}

function resetDemo() {
  syncCurrentToLibrary()
  const { library: lib, demoId } = ensureDemoInLibrary(library.value)
  library.value = setActiveWorkflowId(lib, demoId) ?? lib
  applyWorkflowToCanvas(getActiveWorkflow(library.value))
  ElMessage.success('已切换到示例流程')
}

async function onClearCanvas() {
  if (flowNodes.value.length === 0 && flowEdges.value.length === 0) {
    ElMessage.info('画布已是空的')
    return
  }
  try {
    await ElMessageBox.confirm('确定清空当前流程画布上的所有节点和连线？', '清除画布', {
      type: 'warning',
      confirmButtonText: '清除',
      cancelButtonText: '取消',
      confirmButtonClass: 'el-button--danger'
    })
  } catch {
    return
  }
  flowNodes.value = []
  flowEdges.value = []
  workflow.nodes = []
  workflow.edges = []
  selectedNodeId.value = null
  selectedEdgeId.value = null
  syncCurrentToLibrary()
  ElMessage.success('画布已清空')
}
</script>

<style scoped>
.workflow-editor {
  height: 100%;
  display: flex;
  flex-direction: column;
  background: #f5f7fa;
  border-radius: 8px;
  overflow: hidden;
  border: 1px solid #ebeef5;
}
.wf-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  background: #fff;
  border-bottom: 1px solid #ebeef5;
  flex-shrink: 0;
  flex-wrap: wrap;
}
.wf-toolbar-left,
.wf-toolbar-right {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  flex-wrap: wrap;
}
.wf-toolbar-center {
  flex: 1;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  justify-content: center;
  min-width: 120px;
}
.wf-name-input {
  width: 200px;
}
.wf-issue-tag {
  max-width: 220px;
  overflow: hidden;
  text-overflow: ellipsis;
}
.wf-body {
  flex: 1;
  display: flex;
  min-height: 0;
}
.wf-panel {
  display: flex;
  flex-direction: column;
  background: #fff;
  min-height: 0;
}
.wf-panel-left {
  width: 220px;
  border-right: 1px solid #ebeef5;
  flex-shrink: 0;
}
.wf-palette-body {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.wf-panel-center {
  flex: 1;
  min-width: 0;
}
.wf-panel-right {
  width: 280px;
  border-left: 1px solid #ebeef5;
  flex-shrink: 0;
}
.wf-panel-title {
  padding: 8px 12px;
  font-size: 12px;
  font-weight: 600;
  color: #606266;
  border-bottom: 1px solid #ebeef5;
  flex-shrink: 0;
}
.wf-panel-center :deep(.vue-flow) {
  width: 100%;
  height: 100%;
}
.wf-panel-center :deep(.vue-flow__node) {
  background: transparent !important;
  border: none !important;
  padding: 0 !important;
  box-shadow: none !important;
}
.wf-panel-center :deep(.vue-flow__edge.selected .vue-flow__edge-path) {
  stroke-width: 3 !important;
  filter: drop-shadow(0 0 2px rgba(64, 158, 255, 0.45));
}
.wf-panel-center :deep(.vue-flow__edge.selected .vue-flow__edge-text) {
  fill: #409eff;
  font-weight: 600;
}
</style>
