<script setup>
/**
 * 智能体模板管理
 *
 * 编辑区域完整复刻自 WorkflowEditor.vue：
 *   - VueFlow + slot 注册节点（保证 BaseNode 样式一致，避免白色方框叠加）
 *   - useVueFlow() 拖拽/连线/删除
 *   - useNodeFields composable 处理节点字段增删改
 *   - 节点属性面板（7 种节点类型）
 *
 * 布局：toolbar + 4 列（左 240 模板/元数据 | 160 节点palette | flex canvas | 300 属性面板）
 */
import { ref, reactive, computed, onMounted, onUnmounted, markRaw, nextTick } from 'vue'
import { useRouter } from 'vue-router'
import { VueFlow, useVueFlow } from '@vue-flow/core'
import { Background } from '@vue-flow/background'
import {
  NIcon, NInput, NSelect, NSwitch, NTag, useMessage
} from 'naive-ui'
import {
  ArrowBackOutline, AddOutline, SaveOutline, TrashOutline, ReloadOutline,
  ChatbubbleEllipsesOutline, SparklesOutline, FlaskOutline, RocketOutline,
  GitBranchOutline, ExitOutline, ColorWandOutline
} from '@vicons/ionicons5'
import {
  getAgentTemplates, getAgentTemplate,
  createAgentTemplate, updateAgentTemplate, deleteAgentTemplate
} from '../../api/agentTemplate'
import { getModelList } from '../../api/model'
import { useSkillStore } from '../../stores/skill'
import { useConfirmDelete } from '../../composables/useConfirmDelete'
import { useNodeFields } from '../../composables/useNodeFields'
import { getDefaultData } from '../../utils/workflowNode'
import InputNode from '../../components/workflow/nodes/InputNode.vue'
import LLMNode from '../../components/workflow/nodes/LLMNode.vue'
import AgentNode from '../../components/workflow/nodes/AgentNode.vue'
import SkillNode from '../../components/workflow/nodes/SkillNode.vue'
import OutputNode from '../../components/workflow/nodes/OutputNode.vue'
import ConditionNode from '../../components/workflow/nodes/ConditionNode.vue'
import SmartActionNode from '../../components/workflow/nodes/SmartActionNode.vue'
import NodeThumbnail from '../../components/workflow/NodeThumbnail.vue'

import '@vue-flow/core/dist/style.css'
import '@vue-flow/core/dist/theme-default.css'

const router = useRouter()
const message = useMessage()
const confirmDelete = useConfirmDelete()
const skillStore = useSkillStore()

// ========== Vue Flow 实例 ==========
const {
  onConnect, addEdges, addNodes, removeNodes, removeEdges,
  updateNodeData: vfUpdateNode,
  project, vueFlowRef, getNodes, getEdges, fitView, updateNodeInternals
} = useVueFlow()

const defaultEdgeStyle = { stroke: '#666', strokeWidth: 2 }

// 连线（不引入 ioInference，模板编辑保持简单）
onConnect((params) => {
  addEdges([{
    ...params,
    type: 'bezier',
    animated: true,
    style: defaultEdgeStyle,
    data: {}
  }])
})

// ========== 节点工具栏 ==========
const paletteNodes = [
  { type: 'userInput', label: '用户输入', icon: markRaw(ChatbubbleEllipsesOutline), color: '#10B981' },
  { type: 'llm', label: '大模型处理', icon: markRaw(SparklesOutline), color: '#8B5CF6' },
  { type: 'skill', label: '技能', icon: markRaw(FlaskOutline), color: '#06B6D4' },
  { type: 'agent', label: '智能体', icon: markRaw(RocketOutline), color: '#F59E0B' },
  { type: 'smartAction', label: '智能操作', icon: markRaw(ColorWandOutline), color: '#EC4899' },
  { type: 'condition', label: '条件分支', icon: markRaw(GitBranchOutline), color: '#F97316' },
  { type: 'output', label: '输出', icon: markRaw(ExitOutline), color: '#EF4444' },
]

// ========== 选中状态 ==========
const selectedNode = ref(null)
const selectedEdgeId = ref(null)
const selectedData = computed(() => selectedNode.value?.data || null)
const selectedEdge = computed(() =>
  selectedEdgeId.value ? getEdges.value.find(e => e.id === selectedEdgeId.value) : null
)

function onNodeClick({ node }) {
  selectedNode.value = node
  selectedEdgeId.value = null
}
function onEdgeClick({ edge }) {
  selectedEdgeId.value = edge.id
  selectedNode.value = null
}
function onPaneClick() {
  selectedNode.value = null
  selectedEdgeId.value = null
}

// ========== 模型 / 技能选项 ==========
const dbModels = ref([])
const modelOptions = computed(() =>
  dbModels.value.map(m => ({ label: `${m.name} (${m.modelName})`, value: m.id }))
)
const agentOptions = computed(() =>
  (skillStore.skills || []).map(s => ({
    label: s.displayName || s.translatedDisplayName || s.name || s.folderName,
    value: s.folderName
  }))
)
const failStrategyOptions = [
  { label: '终止工作流', value: 'abort' },
  { label: '跳过继续', value: 'skip' }
]
const IO_TYPES = [
  { label: '字符串', value: 'string' },
  { label: '数字', value: 'number' },
  { label: '布尔', value: 'boolean' },
  { label: '对象', value: 'object' },
  { label: '数组', value: 'array' },
  { label: '文件路径', value: 'filePath' },
  { label: '目录路径', value: 'directoryPath' }
]

async function fetchDbModels() {
  try {
    const res = await getModelList()
    dbModels.value = res.data || []
  } catch (e) {
    message.error('加载模型列表失败')
  }
}

// ========== 字段编辑 composable ==========
function applyEdgeMappingUpdates() { /* 模板编辑不做映射推断，no-op */ }

const {
  onFieldChange, onModelChange,
  addListField, removeListField,
  addVariable, removeVariable,
  addOutputField, removeOutputField,
  addElif, removeElif, onConditionChange
} = useNodeFields({
  selectedNode, selectedData, vfUpdateNode,
  getNodes, getEdges, applyEdgeMappingUpdates,
  skillStore, agentOptions
})

// 兼容模板里调用的旧函数名（addLlmField / removeLlmField / addAgentField / removeAgentField / addConditionInput / removeConditionInput）
const addLlmField = addListField
const removeLlmField = removeListField
const addAgentField = addListField
const removeAgentField = removeListField
const addConditionInput = () => addListField('inputs')
const removeConditionInput = removeListField

// ========== 拖拽 ==========
function onDragStart(e, type) {
  e.dataTransfer.setData('application/vueflow', type)
  e.dataTransfer.effectAllowed = 'move'
}
function onDragOver(e) {
  e.preventDefault()
  e.dataTransfer.dropEffect = 'move'
}
let nodeIdCounter = 0
function genId() { return `node-${++nodeIdCounter}-${Date.now()}` }
function onDrop(e) {
  const type = e.dataTransfer.getData('application/vueflow')
  if (!type) return
  const bounds = vueFlowRef.value.getBoundingClientRect()
  const position = project({ x: e.clientX - bounds.left, y: e.clientY - bounds.top })
  addNodes([{ id: genId(), type, position, data: getDefaultData(type) }])
}

// ========== 删除（键盘） ==========
function onKeyDown(e) {
  if (e.key === 'Delete' || e.key === 'Backspace') {
    const tag = e.target?.tagName
    if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return
    if (selectedNode.value) {
      deleteNodeById(selectedNode.value.id)
    } else if (selectedEdgeId.value) {
      removeEdges(selectedEdgeId.value)
      selectedEdgeId.value = null
    }
  }
}
function deleteNodeById(nodeId) {
  removeNodes(nodeId)
  if (selectedNode.value?.id === nodeId) selectedNode.value = null
}

// ========== 列表 / 表单 ==========
const loading = ref(false)
const saving = ref(false)
const templates = ref([])
const selectedId = ref(null)

const form = reactive({
  id: null, name: '', description: '', mode: 'custom',
  icon: '', sortOrder: 100, enabled: true, isSystem: false,
  graphData: '{"nodes":[],"edges":[]}',
})

const modeOptions = [
  { label: '空白模式', value: 'blank' },
  { label: '工作流编辑模式', value: 'workflow' },
  { label: '自然语言编辑模式', value: 'naturalLanguage' },
  { label: '自定义', value: 'custom' },
]

onMounted(() => {
  fetchList()
  fetchDbModels()
  skillStore.fetchSkills?.()
  window.addEventListener('keydown', onKeyDown)
})

onUnmounted(() => {
  window.removeEventListener('keydown', onKeyDown)
})

async function fetchList() {
  loading.value = true
  try {
    const { data } = await getAgentTemplates(false)
    templates.value = data || []
    if (!selectedId.value && templates.value.length > 0) {
      await selectTemplate(templates.value[0])
    }
  } catch (e) {
    message.error('加载模板列表失败')
  } finally {
    loading.value = false
  }
}

async function selectTemplate(tpl) {
  selectedId.value = tpl.id
  selectedNode.value = null
  selectedEdgeId.value = null
  try {
    const { data } = await getAgentTemplate(tpl.id)
    Object.assign(form, {
      id: data.id,
      name: data.name || '',
      description: data.description || '',
      mode: data.mode || 'custom',
      icon: data.icon || '',
      sortOrder: data.sortOrder ?? 100,
      enabled: data.enabled !== false,
      isSystem: data.isSystem === true,
      graphData: data.graphData || '{"nodes":[],"edges":[]}',
    })
    await applyGraphDataToCanvas(form.graphData)
  } catch (e) {
    message.error('加载模板详情失败')
  }
}

function waitForNodeDimensions(timeout = 2000) {
  return new Promise((resolve) => {
    const start = performance.now()
    const check = () => {
      const nodes = getNodes.value
      if (!nodes.length || nodes.every(n => n.dimensions?.width > 0 && n.dimensions?.height > 0)) {
        resolve()
        return
      }
      if (performance.now() - start > timeout) {
        resolve()
        return
      }
      requestAnimationFrame(check)
    }
    check()
  })
}

function waitForHandles(minCount, timeout = 2000) {
  return new Promise((resolve) => {
    const start = performance.now()
    const check = () => {
      const handles = vueFlowRef.value?.querySelectorAll('.vue-flow__handle') ?? []
      if (handles.length >= minCount) {
        resolve()
        return
      }
      if (performance.now() - start > timeout) {
        resolve()
        return
      }
      requestAnimationFrame(check)
    }
    check()
  })
}

function clearCanvas() {
  const edgeIds = getEdges.value.map(e => e.id)
  if (edgeIds.length) removeEdges(edgeIds)
  const nodeIds = getNodes.value.map(n => n.id)
  if (nodeIds.length) removeNodes(nodeIds)
}

async function applyGraphDataToCanvas(graphData) {
  clearCanvas()
  try {
    const parsed = typeof graphData === 'string' ? JSON.parse(graphData) : graphData
    const nodeList = parsed.nodes || []
    const edgeList = parsed.edges || []

    if (nodeList.length) {
      addNodes(nodeList.map(n => {
        const defaults = getDefaultData(n.type)
        return { ...n, data: { ...defaults, ...n.data, label: defaults.label } }
      }))
    }

    await nextTick()

    if (nodeList.length) {
      // 等待节点里的 Handle 组件挂载到 DOM，再触发 Vue Flow 重新计算句柄位置
      await waitForHandles(nodeList.length * 2)
      updateNodeInternals()
      await waitForNodeDimensions()
    }

    if (edgeList.length) {
      addEdges(edgeList.map(e => ({
        ...e,
        type: 'bezier',
        animated: true,
        style: defaultEdgeStyle,
        data: e.data || {}
      })))
    }

    await nextTick()
    fitView({ padding: 0.3 })
  } catch (e) {
    /* 全局拦截器已处理 */
  }
}

function resetForm() {
  Object.assign(form, {
    id: null, name: '', description: '', mode: 'custom',
    icon: '', sortOrder: 100, enabled: true, isSystem: false,
    graphData: '{"nodes":[],"edges":[]}',
  })
  selectedId.value = null
  selectedNode.value = null
  selectedEdgeId.value = null
  clearCanvas()
}

function handleNew() {
  resetForm()
}

async function handleSave() {
  if (!form.name.trim()) {
    message.warning('请填写模板名称')
    return
  }
  const graphData = JSON.stringify({
    nodes: getNodes.value.map(n => ({
      id: n.id, type: n.type, position: n.position, data: n.data
    })),
    edges: getEdges.value.map(e => ({
      id: e.id, source: e.source, target: e.target,
      sourceHandle: e.sourceHandle, data: e.data
    })),
  })

  const payload = {
    name: form.name.trim(),
    description: form.description,
    mode: form.mode,
    icon: form.icon,
    sortOrder: Number(form.sortOrder) || 0,
    enabled: form.enabled,
    graphData,
  }

  saving.value = true
  try {
    if (form.id) {
      await updateAgentTemplate(form.id, payload)
      message.success('模板已更新')
    } else {
      const { data: created } = await createAgentTemplate(payload)
      message.success('模板已创建')
      form.id = created.id
      form.isSystem = created.isSystem
    }
    await fetchList()
  } catch (e) {
    message.error(e.response?.data?.message || '保存失败')
  } finally {
    saving.value = false
  }
}

function handleDelete(tpl) {
  if (tpl.isSystem) {
    message.warning('系统内置模板不可删除，可禁用')
    return
  }
  confirmDelete({
    name: tpl.name,
    itemType: '模板',
    onConfirm: async () => {
      try {
        await deleteAgentTemplate(tpl.id)
        message.success('删除成功')
        if (selectedId.value === tpl.id) resetForm()
        await fetchList()
      } catch (e) {
        message.error(e.response?.data?.message || '删除失败')
      }
    },
  })
}

function goBack() { router.back() }
</script>

<template>
  <div class="template-mgmt" tabindex="0">
    <!-- 顶部工具栏 -->
    <div class="page-toolbar">
      <div class="toolbar-left">
        <button class="icon-btn" @click="goBack" title="返回">
          <n-icon size="18"><ArrowBackOutline /></n-icon>
        </button>
        <span class="page-title">智能体模板</span>
        <span class="template-count">
          共 <span class="count-num">{{ templates.length }}</span> 个
        </span>
      </div>
      <div class="toolbar-right">
        <n-button size="small" @click="fetchList" :disabled="loading">
          <template #icon><n-icon><ReloadOutline /></n-icon></template>
          刷新
        </n-button>
        <n-button size="small" @click="handleNew">
          <template #icon><n-icon><AddOutline /></n-icon></template>
          新增模板
        </n-button>
        <n-button size="small" type="primary" :disabled="saving" @click="handleSave">
          <template #icon><n-icon><SaveOutline /></n-icon></template>
          {{ saving ? '保存中...' : (form.id ? '保存修改' : '创建模板') }}
        </n-button>
      </div>
    </div>

    <!-- 主体 4 列布局 -->
    <div class="page-body">
      <!-- 列 1：模板列表 + 元数据表单 -->
      <div class="left-panel">
        <!-- 模板列表 -->
        <div class="left-section left-list">
          <div v-if="loading && !templates.length" class="loading-area">加载中...</div>
          <div v-else-if="!templates.length" class="empty-area">暂无模板</div>
          <div
            v-for="tpl in templates"
            :key="tpl.id"
            class="tpl-card"
            :class="{ active: selectedId === tpl.id, disabled: !tpl.enabled }"
            @click="selectTemplate(tpl)"
          >
            <div class="tpl-card-thumb">
              <NodeThumbnail :graph-data="tpl.graphData" :height="48" :padding="6" />
            </div>
            <div class="tpl-card-info">
              <div class="tpl-card-name">
                <span v-if="tpl.icon" class="tpl-icon">{{ tpl.icon }}</span>
                <span>{{ tpl.name }}</span>
              </div>
              <div class="tpl-card-meta">
                <n-tag size="tiny" :type="tpl.isSystem ? 'warning' : 'info'">
                  {{ tpl.isSystem ? '系统' : '自定义' }}
                </n-tag>
                <n-tag size="tiny" :type="tpl.enabled ? 'success' : 'default'">
                  {{ tpl.enabled ? '启用' : '禁用' }}
                </n-tag>
              </div>
            </div>
            <button
              v-if="!tpl.isSystem"
              class="tpl-delete-btn"
              @click.stop="handleDelete(tpl)"
              title="删除"
            >
              <n-icon size="14"><TrashOutline /></n-icon>
            </button>
          </div>
        </div>

        <!-- 元数据表单 -->
        <div class="left-section left-meta">
          <div class="meta-title">模板元数据</div>
          <div class="meta-row">
            <label>名称</label>
            <n-input v-model:value="form.name" placeholder="如：工作流编辑模式" size="small" />
          </div>
          <div class="meta-row">
            <label>描述</label>
            <n-input
              v-model:value="form.description"
              type="textarea"
              :autosize="{ minRows: 1, maxRows: 2 }"
              placeholder="模板简短描述"
              size="small"
            />
          </div>
          <div class="meta-row meta-row-grid">
            <div>
              <label>模式</label>
              <n-select v-model:value="form.mode" :options="modeOptions" size="small" />
            </div>
            <div>
              <label>图标</label>
              <n-input v-model:value="form.icon" placeholder="如 📂" size="small" />
            </div>
          </div>
          <div class="meta-row meta-row-grid">
            <div>
              <label>排序</label>
              <n-input
                :value="String(form.sortOrder)"
                @update:value="v => form.sortOrder = Number(v) || 0"
                size="small"
              />
            </div>
            <div>
              <label>启用</label>
              <div class="switch-row">
                <n-switch v-model:value="form.enabled" size="small" />
                <span class="sys-tag" v-if="form.isSystem">系统内置</span>
              </div>
            </div>
          </div>
          <div class="meta-row meta-id" v-if="form.id">ID: {{ form.id }}</div>
        </div>
      </div>

      <!-- 列 2：节点工具栏 -->
      <div class="palette-panel">
        <div class="palette-title">节点</div>
        <div
          v-for="n in paletteNodes"
          :key="n.type"
          class="palette-item"
          :draggable="true"
          @dragstart="onDragStart($event, n.type)"
        >
          <div class="palette-icon" :style="{ background: n.color }">
            <n-icon size="14"><component :is="n.icon" /></n-icon>
          </div>
          <span>{{ n.label }}</span>
        </div>
      </div>

      <!-- 列 3：画布 -->
      <div class="canvas-area" @drop="onDrop" @dragover="onDragOver">
        <VueFlow
          :default-viewport="{ zoom: 1, x: 0, y: 0 }"
          :min-zoom="0.1"
          :max-zoom="2"
          :elements-selectable="true"
          :snap-to-grid="true"
          :snap-grid="[20, 20]"
          @node-click="onNodeClick"
          @edge-click="onEdgeClick"
          @pane-click="onPaneClick"
        >
          <Background :gap="20" :size="1" pattern-color="rgba(255,255,255,0.05)" />
          <template #node-userInput="nodeProps"><InputNode :data="nodeProps.data" /></template>
          <template #node-llm="nodeProps"><LLMNode :data="nodeProps.data" /></template>
          <template #node-agent="nodeProps"><AgentNode :data="nodeProps.data" /></template>
          <template #node-skill="nodeProps"><SkillNode :data="nodeProps.data" /></template>
          <template #node-output="nodeProps"><OutputNode :data="nodeProps.data" /></template>
          <template #node-condition="nodeProps"><ConditionNode :data="nodeProps.data" /></template>
          <template #node-smartAction="nodeProps"><SmartActionNode :data="nodeProps.data" /></template>
        </VueFlow>
        <div class="canvas-hint">
          拖拽节点到画布 · Delete 键删除选中节点/连线 · 点击空白取消选中
        </div>
      </div>

      <!-- 列 4：属性面板 -->
      <div class="props-panel">
        <div class="props-header">
          <span class="props-title">节点属性</span>
          <span v-if="selectedNode?.type" class="props-type-tag">{{ selectedNode.type }}</span>
        </div>
        <div class="props-content">
          <template v-if="selectedData">
            <!-- 通用：节点显示名 -->
            <div class="prop-section">
              <label class="prop-label">显示名称</label>
              <n-input
                :value="selectedData?.label"
                size="small"
                placeholder="节点名称"
                @update:value="v => onFieldChange('label', v)"
              />
            </div>

            <!-- 用户输入节点 -->
            <template v-if="selectedNode?.type === 'userInput'">
              <div class="prop-section">
                <div class="prop-header">
                  <span class="prop-label">输入变量</span>
                  <button class="text-btn" @click="addVariable">+ 添加</button>
                </div>
                <div v-for="(v, i) in selectedData?.variables || []" :key="i" class="var-form">
                  <div class="var-form-row">
                    <n-input :value="v.name" placeholder="变量名" size="small" @update:value="nv => { v.name = nv; onFieldChange('variables', [...selectedData.variables]) }" />
                    <n-input :value="v.label" placeholder="中文名" size="small" @update:value="nv => { v.label = nv; onFieldChange('variables', [...selectedData.variables]) }" />
                  </div>
                  <div class="var-form-row">
                    <n-select :value="v.type" :options="IO_TYPES" size="small" style="width:100px" @update:value="nv => { v.type = nv; onFieldChange('variables', [...selectedData.variables]) }" />
                    <n-input :value="v.description" placeholder="描述" size="small" @update:value="nv => { v.description = nv; onFieldChange('variables', [...selectedData.variables]) }" />
                  </div>
                  <button class="text-btn danger" @click="removeVariable(i)">删除</button>
                </div>
                <div v-if="!selectedData?.variables?.length" class="empty-hint">暂无变量</div>
              </div>
            </template>

            <!-- LLM 节点 -->
            <template v-if="selectedNode?.type === 'llm'">
              <div class="prop-section">
                <label class="prop-label">模型</label>
                <n-select
                  :value="selectedData?.modelId"
                  :options="modelOptions"
                  placeholder="默认使用默认模型"
                  size="small"
                  clearable
                  @update:value="v => onModelChange(v, dbModels)"
                />
              </div>
              <div class="prop-section">
                <label class="prop-label">系统提示词</label>
                <n-input :value="selectedData?.systemPrompt" type="textarea" :rows="3" placeholder="输入系统提示词" size="small" @update:value="v => onFieldChange('systemPrompt', v)" />
              </div>
              <div class="prop-section">
                <label class="prop-label">用户提示词</label>
                <n-input :value="selectedData?.userPrompt" type="textarea" :rows="3" placeholder="可用 {{变量名}} 引用变量" size="small" @update:value="v => onFieldChange('userPrompt', v)" />
              </div>
              <div class="prop-section">
                <div class="prop-header">
                  <span class="prop-label">输出变量</span>
                  <button class="text-btn" @click="addLlmField('outputs')">+ 添加</button>
                </div>
                <div v-for="(v, i) in selectedData?.outputs || []" :key="'out-'+i" class="var-form">
                  <div class="var-form-row">
                    <n-input :value="v.name" placeholder="变量名" size="small" @update:value="nv => { v.name = nv; onFieldChange('outputs', [...selectedData.outputs]) }" />
                    <n-select :value="v.type" :options="IO_TYPES" size="small" style="width:100px" @update:value="nv => { v.type = nv; onFieldChange('outputs', [...selectedData.outputs]) }" />
                  </div>
                  <div class="var-form-row">
                    <n-input :value="v.label" placeholder="中文名" size="small" @update:value="nv => { v.label = nv; onFieldChange('outputs', [...selectedData.outputs]) }" />
                    <button class="text-btn danger" @click="removeLlmField('outputs', i)">删除</button>
                  </div>
                </div>
                <div v-if="!selectedData?.outputs?.length" class="empty-hint">默认输出 result 变量</div>
              </div>
              <div class="prop-section">
                <label class="prop-label">失败策略</label>
                <n-select :value="selectedData?.failStrategy || 'abort'" :options="failStrategyOptions" size="small" @update:value="v => onFieldChange('failStrategy', v)" />
              </div>
            </template>

            <!-- 智能体节点 -->
            <template v-if="selectedNode?.type === 'agent'">
              <div class="prop-section">
                <label class="prop-label">选择智能体</label>
                <n-select :value="selectedData?.agentId" :options="agentOptions" placeholder="选择技能" size="small" filterable @update:value="v => onFieldChange('agentId', v)" />
              </div>
              <div class="prop-section">
                <label class="prop-label">模型</label>
                <n-select :value="selectedData?.modelId" :options="modelOptions" placeholder="默认使用默认模型" size="small" clearable @update:value="v => onModelChange(v, dbModels)" />
              </div>
              <div class="prop-section">
                <div class="prop-header">
                  <span class="prop-label">前置数据</span>
                  <button class="text-btn" @click="addAgentField('inputs')">+ 添加</button>
                </div>
                <div v-for="(v, i) in selectedData?.inputs || []" :key="'in-'+i" class="var-form">
                  <div class="var-form-row">
                    <n-input :value="v.name" placeholder="变量名" size="small" @update:value="nv => { v.name = nv; onFieldChange('inputs', [...selectedData.inputs]) }" />
                    <n-select :value="v.type" :options="IO_TYPES" size="small" style="width:100px" @update:value="nv => { v.type = nv; onFieldChange('inputs', [...selectedData.inputs]) }" />
                  </div>
                  <div class="var-form-row">
                    <n-input :value="v.label" placeholder="中文名" size="small" @update:value="nv => { v.label = nv; onFieldChange('inputs', [...selectedData.inputs]) }" />
                    <button class="text-btn danger" @click="removeAgentField('inputs', i)">删除</button>
                  </div>
                </div>
                <div v-if="!selectedData?.inputs?.length" class="empty-hint">选择技能后自动填充</div>
              </div>
              <div class="prop-section">
                <div class="prop-header">
                  <span class="prop-label">输出变量</span>
                  <button class="text-btn" @click="addAgentField('outputs')">+ 添加</button>
                </div>
                <div v-for="(v, i) in selectedData?.outputs || []" :key="'out-'+i" class="var-form">
                  <div class="var-form-row">
                    <n-input :value="v.name" placeholder="变量名" size="small" @update:value="nv => { v.name = nv; onFieldChange('outputs', [...selectedData.outputs]) }" />
                    <n-select :value="v.type" :options="IO_TYPES" size="small" style="width:100px" @update:value="nv => { v.type = nv; onFieldChange('outputs', [...selectedData.outputs]) }" />
                  </div>
                  <div class="var-form-row">
                    <n-input :value="v.label" placeholder="中文名" size="small" @update:value="nv => { v.label = nv; onFieldChange('outputs', [...selectedData.outputs]) }" />
                    <button class="text-btn danger" @click="removeAgentField('outputs', i)">删除</button>
                  </div>
                </div>
                <div v-if="!selectedData?.outputs?.length" class="empty-hint">选择技能后自动填充</div>
              </div>
              <div class="prop-section">
                <label class="prop-label">失败策略</label>
                <n-select :value="selectedData?.failStrategy || 'abort'" :options="failStrategyOptions" size="small" @update:value="v => onFieldChange('failStrategy', v)" />
              </div>
            </template>

            <!-- 技能节点 -->
            <template v-if="selectedNode?.type === 'skill'">
              <div class="prop-section">
                <label class="prop-label">选择技能</label>
                <n-select :value="selectedData?.skillId" :options="agentOptions" placeholder="选择技能" size="small" filterable @update:value="v => onFieldChange('skillId', v)" />
              </div>
              <div class="prop-section">
                <label class="prop-label">模型</label>
                <n-select :value="selectedData?.modelId" :options="modelOptions" placeholder="默认使用默认模型" size="small" clearable @update:value="v => onModelChange(v, dbModels)" />
              </div>
              <div class="prop-section">
                <div class="prop-header">
                  <span class="prop-label">前置数据</span>
                  <button class="text-btn" @click="addAgentField('inputs')">+ 添加</button>
                </div>
                <div v-for="(v, i) in selectedData?.inputs || []" :key="'in-'+i" class="var-form">
                  <div class="var-form-row">
                    <n-input :value="v.name" placeholder="变量名" size="small" @update:value="nv => { v.name = nv; onFieldChange('inputs', [...selectedData.inputs]) }" />
                    <n-select :value="v.type" :options="IO_TYPES" size="small" style="width:100px" @update:value="nv => { v.type = nv; onFieldChange('inputs', [...selectedData.inputs]) }" />
                  </div>
                  <div class="var-form-row">
                    <n-input :value="v.label" placeholder="中文名" size="small" @update:value="nv => { v.label = nv; onFieldChange('inputs', [...selectedData.inputs]) }" />
                    <button class="text-btn danger" @click="removeAgentField('inputs', i)">删除</button>
                  </div>
                </div>
                <div v-if="!selectedData?.inputs?.length" class="empty-hint">选择技能后自动填充</div>
              </div>
              <div class="prop-section">
                <div class="prop-header">
                  <span class="prop-label">输出变量</span>
                  <button class="text-btn" @click="addAgentField('outputs')">+ 添加</button>
                </div>
                <div v-for="(v, i) in selectedData?.outputs || []" :key="'out-'+i" class="var-form">
                  <div class="var-form-row">
                    <n-input :value="v.name" placeholder="变量名" size="small" @update:value="nv => { v.name = nv; onFieldChange('outputs', [...selectedData.outputs]) }" />
                    <n-select :value="v.type" :options="IO_TYPES" size="small" style="width:100px" @update:value="nv => { v.type = nv; onFieldChange('outputs', [...selectedData.outputs]) }" />
                  </div>
                  <div class="var-form-row">
                    <n-input :value="v.label" placeholder="中文名" size="small" @update:value="nv => { v.label = nv; onFieldChange('outputs', [...selectedData.outputs]) }" />
                    <button class="text-btn danger" @click="removeAgentField('outputs', i)">删除</button>
                  </div>
                </div>
                <div v-if="!selectedData?.outputs?.length" class="empty-hint">选择技能后自动填充</div>
              </div>
              <div class="prop-section">
                <label class="prop-label">失败策略</label>
                <n-select :value="selectedData?.failStrategy || 'abort'" :options="failStrategyOptions" size="small" @update:value="v => onFieldChange('failStrategy', v)" />
              </div>
            </template>

            <!-- 智能操作节点 -->
            <template v-if="selectedNode?.type === 'smartAction'">
              <div class="prop-section">
                <label class="prop-label">操作要求</label>
                <n-input :value="selectedData?.actionPrompt" type="textarea" :rows="5" placeholder="描述你希望 AI 执行的操作..." size="small" @update:value="v => onFieldChange('actionPrompt', v)" />
              </div>
              <div class="prop-section">
                <label class="prop-label">模型</label>
                <n-select :value="selectedData?.modelId" :options="modelOptions" placeholder="默认使用默认模型" size="small" clearable @update:value="v => onModelChange(v, dbModels)" />
              </div>
              <div class="prop-section">
                <div class="prop-header">
                  <span class="prop-label">前置数据</span>
                  <button class="text-btn" @click="addAgentField('inputs')">+ 添加</button>
                </div>
                <div v-for="(v, i) in selectedData?.inputs || []" :key="'in-'+i" class="var-form">
                  <div class="var-form-row">
                    <n-input :value="v.name" placeholder="变量名" size="small" @update:value="nv => { v.name = nv; onFieldChange('inputs', [...selectedData.inputs]) }" />
                    <n-select :value="v.type" :options="IO_TYPES" size="small" style="width:100px" @update:value="nv => { v.type = nv; onFieldChange('inputs', [...selectedData.inputs]) }" />
                  </div>
                  <div class="var-form-row">
                    <n-input :value="v.label" placeholder="中文名" size="small" @update:value="nv => { v.label = nv; onFieldChange('inputs', [...selectedData.inputs]) }" />
                    <button class="text-btn danger" @click="removeAgentField('inputs', i)">删除</button>
                  </div>
                </div>
                <div v-if="!selectedData?.inputs?.length" class="empty-hint">可用 {{变量名}} 引用</div>
              </div>
              <div class="prop-section">
                <div class="prop-header">
                  <span class="prop-label">输出变量</span>
                  <button class="text-btn" @click="addAgentField('outputs')">+ 添加</button>
                </div>
                <div v-for="(v, i) in selectedData?.outputs || []" :key="'out-'+i" class="var-form">
                  <div class="var-form-row">
                    <n-input :value="v.name" placeholder="变量名" size="small" @update:value="nv => { v.name = nv; onFieldChange('outputs', [...selectedData.outputs]) }" />
                    <n-select :value="v.type" :options="IO_TYPES" size="small" style="width:100px" @update:value="nv => { v.type = nv; onFieldChange('outputs', [...selectedData.outputs]) }" />
                  </div>
                  <div class="var-form-row">
                    <n-input :value="v.label" placeholder="中文名" size="small" @update:value="nv => { v.label = nv; onFieldChange('outputs', [...selectedData.outputs]) }" />
                    <button class="text-btn danger" @click="removeAgentField('outputs', i)">删除</button>
                  </div>
                </div>
                <div v-if="!selectedData?.outputs?.length" class="empty-hint">默认输出 result 变量</div>
              </div>
              <div class="prop-section">
                <label class="prop-label">失败策略</label>
                <n-select :value="selectedData?.failStrategy || 'abort'" :options="failStrategyOptions" size="small" @update:value="v => onFieldChange('failStrategy', v)" />
              </div>
            </template>

            <!-- 条件分支节点 -->
            <template v-if="selectedNode?.type === 'condition'">
              <div class="prop-section">
                <div class="prop-header">
                  <span class="prop-label">条件分支</span>
                  <button class="text-btn" @click="addElif">+ ELIF</button>
                </div>
                <div v-for="(c, i) in selectedData?.conditions || []" :key="i" class="cond-form">
                  <div class="cond-form-row">
                    <span class="cond-tag">{{ c.type }}</span>
                    <n-input :value="c.expression" placeholder="自然语言条件，可用 {{变量名}}" size="small" @update:value="v => onConditionChange(i, 'expression', v)" />
                    <button v-if="c.type === 'ELIF'" class="text-btn danger" @click="removeElif(i)">✕</button>
                  </div>
                </div>
              </div>
              <div class="prop-section">
                <div class="prop-header">
                  <span class="prop-label">前置数据</span>
                  <button class="text-btn" @click="addConditionInput">+ 添加</button>
                </div>
                <div v-for="(v, i) in selectedData?.inputs || []" :key="i" class="var-form">
                  <div class="var-form-row">
                    <n-input :value="v.name" placeholder="变量名" size="small" @update:value="nv => { v.name = nv; onFieldChange('inputs', [...selectedData.inputs]) }" />
                    <n-select :value="v.type" :options="IO_TYPES" size="small" style="width:100px" @update:value="nv => { v.type = nv; onFieldChange('inputs', [...selectedData.inputs]) }" />
                  </div>
                  <div class="var-form-row">
                    <n-input :value="v.label" placeholder="中文名" size="small" @update:value="nv => { v.label = nv; onFieldChange('inputs', [...selectedData.inputs]) }" />
                    <button class="text-btn danger" @click="removeConditionInput('inputs', i)">删除</button>
                  </div>
                </div>
                <div v-if="!selectedData?.inputs?.length" class="empty-hint">连接上游节点后自动推断，或手动添加</div>
                <div class="passthrough-hint">所有前置数据将透传至下游节点</div>
              </div>
            </template>

            <!-- 输出节点 -->
            <template v-if="selectedNode?.type === 'output'">
              <div class="prop-section">
                <div class="prop-header">
                  <span class="prop-label">接收字段</span>
                  <button class="text-btn" @click="addOutputField">+ 添加</button>
                </div>
                <div v-for="(f, i) in selectedData?.fields || []" :key="i" class="var-form">
                  <div class="var-form-row">
                    <n-input :value="f.name" placeholder="字段名" size="small" @update:value="nv => { f.name = nv; onFieldChange('fields', [...selectedData.fields]) }" />
                    <n-select :value="f.type" :options="IO_TYPES" size="small" style="width:100px" @update:value="nv => { f.type = nv; onFieldChange('fields', [...selectedData.fields]) }" />
                  </div>
                  <div class="var-form-row">
                    <n-input :value="f.label" placeholder="中文名" size="small" @update:value="nv => { f.label = nv; onFieldChange('fields', [...selectedData.fields]) }" />
                    <button class="text-btn danger" @click="removeOutputField(i)">删除</button>
                  </div>
                </div>
                <div v-if="!selectedData?.fields?.length" class="empty-hint">连接上游节点后自动推断，或手动添加</div>
              </div>
            </template>
          </template>

          <!-- 边信息 -->
          <template v-else-if="selectedEdge">
            <div class="prop-section">
              <label class="prop-label">连线</label>
              <div class="edge-info">
                <div>源：{{ selectedEdge.source }}</div>
                <div>目标：{{ selectedEdge.target }}</div>
              </div>
              <button class="text-btn danger" @click="() => { removeEdges(selectedEdge.id); selectedEdgeId = null }">删除连线</button>
            </div>
          </template>

          <div v-else class="empty-props-hint">点击节点或连线查看详情</div>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.template-mgmt {
  height: 100%;
  display: flex;
  flex-direction: column;
  background: #13111a;
  outline: none;
}

/* ========== 工具栏 ========== */
.page-toolbar {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 20px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  background: #1a1a2e;
  flex-shrink: 0;
}
.toolbar-left, .toolbar-right {
  display: flex;
  align-items: center;
  gap: 8px;
}
.toolbar-left { gap: 12px; }
.page-title {
  font-size: 16px;
  font-weight: 600;
  color: var(--text-primary, #fff);
}
.template-count {
  font-size: 12px;
  color: var(--text-secondary, #94a3b8);
}
.count-num { color: #38bdf8; font-weight: 600; }

.icon-btn {
  width: 32px;
  height: 32px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--text-secondary, #94a3b8);
  cursor: pointer;
  border-radius: 6px;
}
.icon-btn:hover {
  color: var(--text-primary, #fff);
  background: rgba(255, 255, 255, 0.06);
}

/* ========== 4 列主体 ========== */
.page-body {
  flex: 1;
  display: grid;
  grid-template-columns: 260px 160px 1fr 300px;
  overflow: hidden;
  min-height: 0;
}

/* ========== 列 1：左侧 ========== */
.left-panel {
  display: flex;
  flex-direction: column;
  border-right: 1px solid rgba(255, 255, 255, 0.06);
  background: #1a1a2e;
  min-height: 0;
}
.left-section {
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.left-list {
  flex: 1;
  overflow-y: auto;
  padding: 8px;
  gap: 6px;
}
.left-meta {
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  padding: 12px;
  background: #16162a;
  max-height: 320px;
  overflow-y: auto;
  flex-shrink: 0;
}
.meta-title {
  font-size: 12px;
  font-weight: 600;
  color: var(--text-secondary, #94a3b8);
  margin-bottom: 10px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.meta-row {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 10px;
}
.meta-row label {
  font-size: 11px;
  color: var(--text-secondary, #94a3b8);
}
.meta-row-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 8px;
}
.meta-row-grid > div {
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.switch-row {
  display: flex;
  align-items: center;
  gap: 6px;
  height: 28px;
}
.sys-tag {
  font-size: 10px;
  color: #f59e0b;
  padding: 1px 5px;
  background: rgba(245, 158, 11, 0.12);
  border-radius: 3px;
}
.meta-id {
  font-size: 11px;
  color: var(--text-muted, #64748b);
  font-family: monospace;
  margin: 0;
}

.loading-area, .empty-area {
  padding: 16px;
  text-align: center;
  color: var(--text-muted, #64748b);
  font-size: 12px;
}

/* 模板卡片 */
.tpl-card {
  position: relative;
  background: rgba(255, 255, 255, 0.02);
  border: 1px solid rgba(255, 255, 255, 0.06);
  border-radius: 6px;
  padding: 8px;
  cursor: pointer;
  display: flex;
  gap: 10px;
  align-items: center;
  transition: all 0.15s;
  margin-bottom: 4px;
}
.tpl-card:hover {
  border-color: rgba(56, 189, 248, 0.3);
  background: rgba(56, 189, 248, 0.05);
}
.tpl-card.active {
  border-color: #2563eb;
  background: rgba(37, 99, 235, 0.12);
}
.tpl-card.disabled { opacity: 0.5; }

.tpl-card-thumb {
  width: 56px;
  height: 48px;
  background: rgba(15, 23, 42, 0.5);
  border-radius: 4px;
  border: 1px solid rgba(56, 189, 248, 0.06);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  overflow: hidden;
}
.tpl-card-info {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.tpl-card-name {
  display: flex;
  align-items: center;
  gap: 4px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-primary, #fff);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.tpl-icon { font-size: 14px; flex-shrink: 0; }
.tpl-card-meta {
  display: flex;
  gap: 3px;
  flex-wrap: wrap;
}

.tpl-delete-btn {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 20px;
  height: 20px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--text-muted, #64748b);
  cursor: pointer;
  border-radius: 3px;
  opacity: 0;
  transition: all 0.15s;
}
.tpl-card:hover .tpl-delete-btn { opacity: 1; }
.tpl-delete-btn:hover {
  color: #ef4444;
  background: rgba(239, 68, 68, 0.15);
}

/* ========== 列 2：节点工具栏 ========== */
.palette-panel {
  background: #1a1a2e;
  border-right: 1px solid rgba(255, 255, 255, 0.06);
  padding: 10px 8px;
  overflow-y: auto;
}
.palette-title {
  font-size: 11px;
  color: var(--text-secondary, #94a3b8);
  font-weight: 600;
  margin-bottom: 8px;
  padding: 0 4px;
  text-transform: uppercase;
  letter-spacing: 0.5px;
}
.palette-item {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 4px;
  cursor: grab;
  font-size: 12px;
  color: var(--text-secondary, #94a3b8);
  transition: all 0.15s;
  margin-bottom: 2px;
}
.palette-item:hover {
  background: rgba(56, 189, 248, 0.08);
  color: var(--text-primary, #fff);
}
.palette-item:active { cursor: grabbing; }
.palette-icon {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  flex-shrink: 0;
}

/* ========== 列 3：画布 ========== */
.canvas-area {
  position: relative;
  background: #0f0d17;
  min-height: 0;
  min-width: 0;
}
.canvas-area :deep(.vue-flow) {
  background: #0f0d17;
}
/* 关键：覆盖 VueFlow 默认节点的白色背景，避免叠加方框 */
.canvas-area :deep(.vue-flow__node) {
  background: transparent !important;
  padding: 0 !important;
  border: none !important;
  box-shadow: none !important;
}
.canvas-area :deep(.vue-flow__edge.selected .vue-flow__edge-path) {
  stroke: #facc15 !important;
  stroke-width: 3 !important;
}
.canvas-area :deep(.vue-flow__edge:hover .vue-flow__edge-path) {
  stroke: #facc15 !important;
}
.canvas-area :deep(.vue-flow__edge) {
  cursor: pointer;
}
.canvas-hint {
  position: absolute;
  bottom: 12px;
  left: 50%;
  transform: translateX(-50%);
  font-size: 11px;
  color: rgba(148, 163, 184, 0.6);
  background: rgba(0, 0, 0, 0.5);
  padding: 4px 12px;
  border-radius: 12px;
  pointer-events: none;
  white-space: nowrap;
}

/* ========== 列 4：属性面板 ========== */
.props-panel {
  background: #1a1a2e;
  border-left: 1px solid rgba(255, 255, 255, 0.06);
  display: flex;
  flex-direction: column;
  min-height: 0;
}
.props-header {
  padding: 12px 16px;
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}
.props-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-primary, #fff);
}
.props-type-tag {
  font-size: 10px;
  padding: 1px 6px;
  background: rgba(56, 189, 248, 0.12);
  color: #38bdf8;
  border-radius: 3px;
  font-family: monospace;
}
.props-content {
  flex: 1;
  overflow-y: auto;
  padding: 12px;
}
.empty-props-hint {
  text-align: center;
  color: var(--text-muted, #64748b);
  font-size: 12px;
  padding: 32px 12px;
}

.prop-section {
  margin-bottom: 14px;
}
.prop-label {
  display: block;
  font-size: 11px;
  color: var(--text-secondary, #94a3b8);
  margin-bottom: 4px;
  font-weight: 500;
}
.prop-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 6px;
}
.prop-header .prop-label { margin-bottom: 0; }

.text-btn {
  background: transparent;
  border: none;
  color: #38bdf8;
  font-size: 11px;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: 3px;
}
.text-btn:hover { background: rgba(56, 189, 248, 0.1); }
.text-btn.danger { color: #ef4444; }
.text-btn.danger:hover { background: rgba(239, 68, 68, 0.1); }

.var-form {
  background: rgba(0, 0, 0, 0.2);
  padding: 6px;
  border-radius: 4px;
  margin-bottom: 6px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.var-form-row {
  display: flex;
  gap: 4px;
}
.var-form-row > * { flex: 1; }

.cond-form {
  background: rgba(0, 0, 0, 0.2);
  padding: 6px;
  border-radius: 4px;
  margin-bottom: 6px;
}
.cond-form-row {
  display: flex;
  align-items: center;
  gap: 6px;
}
.cond-form-row > .n-input { flex: 1; }
.cond-tag {
  font-size: 10px;
  font-weight: 600;
  color: #f97316;
  background: rgba(249, 115, 22, 0.12);
  padding: 2px 6px;
  border-radius: 3px;
  flex-shrink: 0;
}

.empty-hint {
  font-size: 11px;
  color: var(--text-muted, #64748b);
  padding: 8px;
  text-align: center;
}
.passthrough-hint {
  font-size: 10px;
  color: #38bdf8;
  padding: 4px 8px;
  background: rgba(56, 189, 248, 0.06);
  border-radius: 3px;
  margin-top: 6px;
}

.edge-info {
  font-size: 11px;
  color: var(--text-secondary, #94a3b8);
  background: rgba(0, 0, 0, 0.2);
  padding: 6px 8px;
  border-radius: 4px;
  margin-bottom: 8px;
  font-family: monospace;
  line-height: 1.6;
}
</style>
