<template>
  <div class="page">
    <el-container class="page__container">
      <el-header v-if="!flowInfoData.isLog">
        <div class="flow-heading">
          <div class="flow-heading__name">{{ flowInfoData.name || "未命名流程" }}</div>
          <div class="flow-status" :class="`flow-status--${flowState}`">
            <span class="flow-status__dot"></span>
            <span>{{ stateMap[flowState] }}</span>
          </div>
        </div>
        <div class="btn__container">
          <div class="btn flow-actions">
            <el-tag v-if="hasRunResult && ['testRunError', 'testRunFinish'].includes(flowState)" :type="flowState === 'testRunFinish' ? 'success' : 'danger'">
              {{ totalRunningTime }}ms
            </el-tag>
            <el-button v-if="hasRunResult" @click="clearRun">
              <el-icon><Delete /></el-icon>清除结果
            </el-button>
            <el-button :type="groupSelectionPending ? 'primary' : 'default'" @click="group">
              <el-icon><CollectionTag /></el-icon>{{ groupSelectionPending ? "取消分组" : "分组" }}
            </el-button>
            <el-button v-if="flowState !== 'testRunning'" @click="testRun">
              <el-icon><VideoPlay /></el-icon>试运行
            </el-button>
            <el-button v-if="flowState === 'testRunning'" @click="flowPauseFn">
              <el-icon><VideoPause /></el-icon>暂停
            </el-button>
            <el-button v-if="flowState === 'pause'" @click="flowResumeFn">
              <el-icon><VideoPlay /></el-icon>恢复
            </el-button>
            <el-button v-if="['testRunning', 'pause'].includes(flowState)" @click="flowStopFn">
              <el-icon><CircleClose /></el-icon>终止
            </el-button>
          </div>
          <div class="btn publish-actions">
            <el-tooltip
              class="box-item"
              effect="dark"
              content="试运行成功后才可以发布"
              placement="top"
              v-if="flowState !== 'testRunFinish'"
            >
              <el-button type="primary" :disabled="true"><el-icon><Upload /></el-icon>发布</el-button>
            </el-tooltip>
            <el-button v-else @click="deployFlow" type="primary"><el-icon><Upload /></el-icon>发布</el-button>

            <el-popover
              :width="260"
              @show="showNodeRelationship"
              popper-class="node-relationship-network-popover"
            >
              <template #reference>
                <el-button circle title="节点关系" aria-label="节点关系"><el-icon><Share /></el-icon></el-button>
              </template>
              <div>
                <div class="title">节点关系网</div>
                <div class="fit">
                  <el-tooltip
                    class="box-item"
                    effect="dark"
                    content="适应屏幕"
                    placement="bottom"
                  >
                    <el-button size="small" circle @click="fitView"
                      ><el-icon><Location /></el-icon
                    ></el-button>
                  </el-tooltip>
                </div>
                <el-tree
                  style="max-width: 600px"
                  :data="treeData"
                  :props="defaultProps"
                  :default-expand-all="true"
                  @node-click="handleTreeNodeClick"
                />
              </div>
            </el-popover>
          </div>
        </div>
      </el-header>
      <el-container class="container">
        <el-aside v-if="!flowStore.disableForm">
          <Aside />
        </el-aside>
        <el-main>
          <div class="canvas-toolbar">
            <el-tooltip v-if="!flowStore.disableForm" content="撤销" placement="bottom">
              <el-button aria-label="撤销" @click="lf.undo()"><el-icon><RefreshLeft /></el-icon></el-button>
            </el-tooltip>
            <el-tooltip v-if="!flowStore.disableForm" content="重做" placement="bottom">
              <el-button aria-label="重做" @click="lf.redo()"><el-icon><RefreshRight /></el-icon></el-button>
            </el-tooltip>
            <span v-if="!flowStore.disableForm" class="canvas-toolbar__divider"></span>
            <el-tooltip content="适应视图" placement="bottom">
              <el-button aria-label="适应视图" @click="fitView"><el-icon><FullScreen /></el-icon></el-button>
            </el-tooltip>
          </div>
          <VueFlow
            id="main-flow"
            class="flow__container"
            ref="flowContainerRef"
            :min-zoom="0.1"
            :max-zoom="3"
            :node-drag-threshold="0"
            :nodes-draggable="!flowStore.disableForm"
            :nodes-connectable="!flowStore.disableForm"
            :edges-updatable="!flowStore.disableForm"
            :elements-selectable="true"
            :elevate-nodes-on-select="false"
            :delete-key-code="null"
            :multi-selection-key-code="['Control', 'Meta']"
            :selection-key-code="groupSelectionPending"
            :pan-on-drag="!groupSelectionPending"
            :zoom-on-double-click="false"
            :default-edge-options="defaultEdgeOptions"
            @connect="handleConnect"
            @node-double-click="handleNodeDoubleClick"
            @node-drag-start="handleNodeDragStart"
            @node-drag="handleNodeDrag"
            @node-drag-stop="handleNodeDragStop"
            @nodes-change="handleNodesChange"
            @edges-change="handleEdgesChange"
            @edge-click="handleEdgeClick"
            @pane-click="handlePaneClick"
            @selection-end="handleSelectionEnd"
            @dragover="dragover"
            @drop="drop"
          >
            <template #node-flow-node="nodeProps">
              <FlowNode v-bind="nodeProps" />
            </template>
            <template #edge-flow-edge="edgeProps">
              <FlowEdge v-bind="edgeProps" />
            </template>
            <Background color="#d9dee8" :gap="24" :size="1" />
            <Controls position="bottom-right" />
          </VueFlow>
        </el-main>
      </el-container>
    </el-container>

    <TestRun
      :drawer="isOpen"
      @close="isOpen = false"
      @changeState="changeState"
      :flowId="flowInfoData.itemId"
    />

    <ParamsDrawer
      v-if="showParamsDrawer"
      :drawer="true"
      :data="paramsDrawerData"
      @close="showParamsDrawer = false"
    />
  </div>

  <el-dialog v-model="visible" width="500" append-to-body>
    <template #header="{ close, titleId, titleClass }">
      <div class="my-header">
        <h4 :id="titleId" :class="titleClass">执行{{ nodeName }}节点</h4>
      </div>
    </template>
    <el-form
      :inline="true"
      :model="formData"
      :rules="rules"
      ref="executeForm"
      label-position="top"
      label-width="auto"
      :disabled="flowStore.disableForm"
    >
      <div v-for="(property, index) in formData.nodeParams" :key="index">
        <el-row>
          <el-form-item
            :label="index === 0 ? '参数名' : ''"
            :prop="`nodeParams.${index}.name`"
            :rules="[{ required: true, message: '请输入参数名', trigger: 'blur' }]"
          >
            <el-input
              :disabled="property?.disabled || false"
              v-model="property.name"
              placeholder="请输入"
              clearable
            />
          </el-form-item>
          <el-form-item
            :label="index === 0 ? '参数值' : ''"
            :prop="`nodeParams.${index}.input`"
            :rules="[{ required: true, message: '请输入参数值', trigger: 'blur' }]"
          >
            <el-input-number
              v-if="property.componentType === 'number'"
              v-model="property.input"
              :min="0"
              :max="property.max || Infinity"
              :step="property.step || 1"
              :step-strictly="true"
              placeholder="请输入"
              clearable
            />
            <el-select
              v-model="property.input"
              v-else-if="property.componentType === 'select'"
            >
              <el-option
                v-for="item in property.selectOptions"
                :key="item.value"
                :label="item.label"
                :value="item.value"
              />
            </el-select>
            <el-input
              v-else
              v-model="property.input"
              autosize
              type="textarea"
              placeholder="请输入"
              clearable
            />
          </el-form-item>
        </el-row>
      </div>
    </el-form>
    <template #footer>
      <div class="dialog-footer">
        <el-button @click="visible = false">取消</el-button>
        <el-button type="primary" @click="execute"> 确定 </el-button>
      </div>
    </template>
  </el-dialog>
</template>
<script setup>
import { computed, reactive, ref, onMounted, onUnmounted, nextTick } from "vue";
import { MarkerType, VueFlow, 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 FlowNode from "./vue-flow/FlowNode.vue";
import FlowEdge from "./vue-flow/FlowEdge.vue";
import { createFlowFacade } from "./vue-flow/adapter";
import { useFlowEvents } from "./composables/useFlowEvents";
import TestRun from "./components/TestRun.vue";
import ParamsDrawer from "./components/ParamsDrawer.vue";
import Aside from "./components/Aside.vue";
import { ElMessage } from "element-plus";
import { useFlowStore } from "@/store/modules/flow";
import { emitter } from "@/utils/eventBus";
import { convertToTree } from "@/utils/flow";
import {
  flowDeploy,
  flowView,
  flowPause,
  flowResume,
  flowStop,
  flowAction,
} from "@/api/device/flow";
import { getDetect } from "@/api/test/detect";
import {
  CircleClose,
  CollectionTag,
  Delete,
  FullScreen,
  Location,
  RefreshLeft,
  RefreshRight,
  Share,
  Upload,
  VideoPause,
  VideoPlay,
} from "@element-plus/icons-vue";

const flowContainerRef = ref(null);
const lfRef = ref(null);
const vueFlow = useVueFlow("main-flow");
const lf = createFlowFacade(vueFlow);
const loopChildDragContexts = new Map();
const { onFlowEvent } = useFlowEvents();
window.lf = lf;

const defaultEdgeOptions = {
  type: "flow-edge",
  animated: true,
  selectable: true,
  focusable: true,
  deletable: true,
  interactionWidth: 32,
  markerEnd: {
    type: MarkerType.ArrowClosed,
    color: "#ff6b35",
    width: 20,
    height: 20,
  },
  style: { stroke: "#ff6b35", strokeWidth: 2 },
};

const dragover = (e) => {
  e.preventDefault();
};

const drop = (e) => {
  let node = e.dataTransfer.getData("flowNode");
  if (!node) return;
  node = JSON.parse(node);
  const point = lf.getPointByClient(e.clientX, e.clientY);
  const loopNode = findLoopAtPoint(point.canvasOverlayPosition);
  if (["stopLoop", "subEnd", "subStart", "currentLoop"].includes(node.type) && !loopNode) {
    ElMessage.warning("此节点只能拖拽到循环节点的循环体中");
    return;
  }
  const { type, ...other } = node;
  const newNode = lf.addNode({
    type,
    x: point.canvasOverlayPosition.x,
    y: point.canvasOverlayPosition.y,
    properties: {
      ...other,
    },
  });
  if (loopNode && !["start", "end", "loop", "selectArea"].includes(newNode.type)) {
    lf.addToGroup(loopNode.id, newNode.id, point.canvasOverlayPosition);
  }

  if (
    !["selectArea", "branch", "stopLoop", "subStart", "subEnd"].includes(newNode.type)
  ) {
    showParamsDrawer.value = true;
    paramsDrawerData.value = newNode;
  }
};

const handleConnect = (connection) => {
  if (!connection.source || !connection.target) return;
  const edge = lf.addEdge({
    type: "bezier",
    sourceNodeId: connection.source,
    targetNodeId: connection.target,
    sourceAnchorId: connection.sourceHandle || `${connection.source}_1`,
    targetAnchorId: connection.targetHandle || `${connection.target}_3`,
  });
  lf.emit("edge:add", { data: edge });
};

const handleNodeDoubleClick = ({ node }) => {
  const data = lf.getGraphData().nodes.find((item) => item.id === node.id);
  if (!data || ["selectArea", "branch", "stopLoop", "subStart", "subEnd", "end"].includes(data.type)) return;
  showParamsDrawer.value = true;
  paramsDrawerData.value = data;
};

const findLoopAtPoint = (point) => {
  return (vueFlow.getNodes.value || []).find((candidate) => {
    if (candidate.data?.flowType !== "loop") return false;
    const groupPosition = candidate.computedPosition || candidate.position;
    const groupWidth = Number(candidate.dimensions?.width || candidate.data?.size?.width || 0);
    const groupHeight = Number(candidate.dimensions?.height || candidate.data?.size?.height || 0);
    return point.x >= groupPosition.x + 24
      && point.x <= groupPosition.x + groupWidth - 24
      && point.y >= groupPosition.y + 112
      && point.y <= groupPosition.y + groupHeight - 24;
  });
};

const findContainingLoop = (node) => {
  const position = node.computedPosition || node.position;
  const width = Number(node.dimensions?.width || node.data?.size?.width || 0);
  const height = Number(node.dimensions?.height || node.data?.size?.height || 0);
  return findLoopAtPoint({ x: position.x + width / 2, y: position.y + height / 2 });
};

const eventClientPoint = (event) => {
  const pointer = event?.touches?.[0] || event?.changedTouches?.[0] || event;
  if (!Number.isFinite(pointer?.clientX) || !Number.isFinite(pointer?.clientY)) return null;
  return { x: pointer.clientX, y: pointer.clientY };
};

const handleNodeDragStart = ({ node, event }) => {
  const drag = lf.prepareLoopChildDrag(node.id);
  const clientPoint = eventClientPoint(event);
  if (!drag || !clientPoint) return;
  const pointer = lf.getPointByClient(clientPoint.x, clientPoint.y).canvasOverlayPosition;
  loopChildDragContexts.set(node.id, {
    groupId: drag.groupId,
    offset: { x: pointer.x - drag.position.x, y: pointer.y - drag.position.y },
  });
};

const updateLoopChildDrag = ({ node, event }) => {
  const drag = loopChildDragContexts.get(node.id);
  const clientPoint = eventClientPoint(event);
  if (!drag || !clientPoint) return;
  const pointer = lf.getPointByClient(clientPoint.x, clientPoint.y).canvasOverlayPosition;
  lf.layoutLoopDuringDrag(drag.groupId, node.id, {
    x: pointer.x - drag.offset.x,
    y: pointer.y - drag.offset.y,
  });
};

const handleNodeDrag = (payload) => updateLoopChildDrag(payload);

const handleNodeDragStop = (payload) => {
  const { node } = payload;
  updateLoopChildDrag(payload);
  loopChildDragContexts.delete(node.id);
  const vueNode = vueFlow.findNode(node.id);
  if (
    vueNode
    && !vueNode.parentNode
    && !["start", "end", "loop", "selectArea"].includes(vueNode.data?.flowType)
  ) {
    const loopNode = findContainingLoop(vueNode);
    if (loopNode) lf.addToGroup(loopNode.id, vueNode.id);
  }

  lf.markChanged();
};

const handleNodesChange = (changes) => {
  if (changes.some((change) => ["add", "remove"].includes(change.type) || (change.type === "position" && change.dragging === false))) {
    lf.markChanged();
  }
};

const handleEdgesChange = (changes) => {
  if (changes.some((change) => ["add", "remove"].includes(change.type))) lf.markChanged();
};

const handleEdgeClick = ({ event, edge }) => {
  event?.stopPropagation?.();
  lf.selectElementById(edge.id);
};

const handlePaneClick = () => lf.clearSelectElements();

const flowStore = useFlowStore();
flowStore.getDeviceList();

const isOpen = ref(false);
const stateMap = {
  unpublished: "未发布",
  testRunning: "试运行中",
  testRunFinish: "试运行成功",
  testRunError: "试运行失败",
  release: "已发布",
  updateData: "修改中",
  pause: "已暂停",
  stop: "已终止",
};
const flowState = ref("unpublished");
const flowInfoData = ref({
  itemId: "",
  instId: "",
  taskId: "",
  isLog: false,
  name: "流程",
});

/**
 * 试运行
 */
const testRun = async () => {
  // 1、验证画布内所有的表单通过验证
  const { nodes, edges } = lf.getGraphData();

  const isolatedNodes = nodes.filter((node) => {
    const nodeId = node.id;
    // 检查是否存在关联边
    return !edges.some(
      (edge) => edge.sourceNodeId === nodeId || edge.targetNodeId === nodeId
    );
  });

  const gl = isolatedNodes.filter((item) => {
    return !item.properties?.parentId && item.type !== "selectArea";
  });

  if (gl && gl.length > 0) {
    ElMessage.warning("存在孤立的节点，请检查！");
    return;
  }

  if (nodeErrorList.value.length > 0) {
    ElMessage.warning("存在参数错误的节点，请检查！");
    return;
  }

  isOpen.value = true;
};

// 流程暂停
const flowPauseFn = async () => {
  const res = await flowPause(instId.value);
  if (res.code === 200) {
    flowState.value = "pause";
    ElMessage.success(res.msg);
  }
};

const totalRunningTime = ref(0);
const hasRunResult = ref(false);
let flowPollTimer = null;
let flowDisposed = false;

const clearFlowPollTimer = () => {
  if (flowPollTimer !== null) {
    window.clearTimeout(flowPollTimer);
    flowPollTimer = null;
  }
};

const scheduleFlowView = (currentInstId, taskId = null, delay = 2000) => {
  clearFlowPollTimer();
  flowPollTimer = window.setTimeout(() => {
    flowPollTimer = null;
    if (!flowDisposed) loopFlowView(currentInstId, taskId);
  }, delay);
};

const flowResumeFn = async () => {
  const res = await flowResume(instId.value);
  if (res.code === 200) {
    flowState.value = "testRunning";
    clearFlowPollTimer();
    loopFlowView(instId.value);
    ElMessage.success(res.msg);
  }
};

const flowStopFn = async () => {
  const res = await flowStop(instId.value);
  if (res.code === 200) {
    clearFlowPollTimer();
    flowState.value = "stop";
    hasRunResult.value = true;
    flowStore.updateDisableForm(false);
    ElMessage.success(res.msg);
  }
};

const initFlowData = {
  nodes: [
    {
      type: "start",
      x: 300,
      y: 100,
      properties: {
        name: "Start",
        action: "start",
      },
    },
    {
      type: "end",
      x: 1300,
      y: 700,
      properties: {
        name: "End",
        action: "end",
      },
    },
  ],
  // 边
  edges: [],
};

const hasSameEdge = (edge, ignoredId) => lf.getGraphData().edges.some((item) => (
  item.id !== ignoredId
  && item.sourceNodeId === edge.sourceNodeId
  && item.targetNodeId === edge.targetNodeId
  && item.sourceAnchorId === edge.sourceAnchorId
  && item.targetAnchorId === edge.targetAnchorId
));

const validEdge = (data) => {
  const sourceModel = lf.getNodeModelById(data.sourceNodeId);
  const targetModel = lf.getNodeModelById(data.targetNodeId);
  if (!sourceModel || !targetModel) {
    lf.deleteEdge(data.id);
    return;
  }

  const sourceAnchorArr = sourceModel.anchors;
  const [sourceName] = sourceAnchorArr
    .filter((item) => {
      return item.id === data.sourceAnchorId;
    })
    .map((item) => {
      return item.name;
    });

  const targetAnchorArr = targetModel.anchors;
  const [targetName] = targetAnchorArr
    .filter((item) => {
      return item.id === data.targetAnchorId;
    })
    .map((item) => {
      return item.name;
    });

  if (!sourceName || !targetName) {
    lf.deleteEdge(data.id);
    return;
  }

  if (sourceName === "right" && targetName === "left") {
    if (hasSameEdge(data, data.id)) lf.deleteEdge(data.id);
    return;
  } else if (
    (sourceName === "right" && targetName === "right") ||
    (sourceName === "left" && targetName === "left")
  ) {
    lf.deleteEdge(data.id);
  } else if (sourceName === "left" && targetName === "right") {
    const sourceNodeId = data.targetNodeId;
    const sourceAnchorId = data.targetAnchorId;
    const targetNodeId = data.sourceNodeId;
    const targetAnchorId = data.sourceAnchorId;
    lf.deleteEdge(data.id);
    if (hasSameEdge({ sourceNodeId, targetNodeId, sourceAnchorId, targetAnchorId })) return;
    lf.addEdge({
      type: "bezier",
      sourceNodeId,
      targetNodeId,
      sourceAnchorId,
      targetAnchorId,
    });
  }
};

const instId = ref(null);
const changeState = (value) => {
  const flowData = JSON.parse(JSON.stringify(lf.getGraphData()));
  oldFlowData = flowData;
  isOpen.value = false;
  flowStore.updateDisableForm(true);
  flowState.value = value.type;
  hasRunResult.value = false;

  instId.value = value.instId;
  clearFlowPollTimer();
  loopFlowView(value.instId);
};

const loopFlowView = async (instId, taskId = null) => {
  const params = {
    itemId: flowInfoData.value.itemId,
    instId,
  };
  if (taskId) {
    params.taskId = taskId;
  }
  const res = await flowView(params);
  if (flowDisposed) return;
  if (res.code === 200) {
    const arr = res.data || [];
    if (arr.length > 0) hasRunResult.value = true;
    arr.forEach((item) => {
      emitter.emit("changeNodeState", item);
    });
    oldFlowData = JSON.parse(JSON.stringify(lf.getGraphData()));
    if (
      arr[arr.length - 1]?.nodeType !== "END" &&
      !["FAILED", "STOPPED"].includes(arr[arr.length - 1]?.status)
    ) {
      scheduleFlowView(instId);
    } else if (
      arr[arr.length - 1]?.nodeType === "END" &&
      arr[arr.length - 1]?.status === "SUCCESS"
    ) {
      if (!flowInfoData.value.isLog) {
        flowStore.updateDisableForm(false);
        flowState.value = "testRunFinish";
        totalRunningTime.value = arr[arr.length - 1].endTime - arr[0].startTime
      }
    } else {
      if (!flowInfoData.value.isLog) {
        if (["PAUSED", "STOPPED"].includes(arr[arr.length - 1]?.status)) {
          flowState.value = arr[arr.length - 1]?.status === "PAUSED" ? "pause" : "stop";
        } else {
          flowState.value = "testRunError";
          totalRunningTime.value = arr[arr.length - 1].endTime - arr[0].startTime
          flowStore.updateDisableForm(false);
        }
      }
    }
  }
};

let oldFlowData = {};
/**
 * 发布
 */
const deployFlow = async () => {
  if (flowState.value !== "testRunFinish") {
    ElMessage.warning("试运行成功后才可以发布");
    return;
  }

  try {
    const res = await flowDeploy({ id: flowInfoData.value.itemId });

    if (res.code === 200) {
      ElMessage.success(res.msg);
      flowState.value = "release";
    } else {
      ElMessage.error(res.msg);
    }
  } catch (error) {
    ElMessage.error("部署操作产生异常");
  }
};

const loadFlowData = async (id, lf) => {
  try {
    const res = await getDetect(id);
    if (res.data && res.data.flowData) {
      const flowData = JSON.parse(res.data.flowData);
      lf.render(flowData);

      nextTick(() => {
        oldFlowData = JSON.parse(JSON.stringify(lf.getGraphData()));
        lf.fitView();
      });
    } else {
      // 渲染数据 data
      lf.render(initFlowData);
      oldFlowData = initFlowData;
      nextTick(() => lf.zoom(0.8, [850, 450]));
    }
  } catch (err) {
    lf.render(initFlowData);
    oldFlowData = initFlowData;
    nextTick(() => lf.zoom(0.8, [850, 450]));
  }
};

const registerBeforeUnload = () => {
  window.addEventListener("beforeunload", handleBeforeUnload);
};

const handleBeforeUnload = (event) => {
  if (flowState.value === "pause" && !flowInfoData.value.isLog) {
    flowStopFn();
  }
};

const justLoadFlow = () => {
  const url = new URL(window.location.href);
  const locationParams = Object.fromEntries(url.searchParams.entries());
  if (locationParams?.itemId) {
    flowState.value = locationParams.state === "0" ? "release" : "unpublished";
    flowInfoData.value = {
      itemId: locationParams.itemId,
      instId: locationParams?.instId || "",
      taskId: locationParams?.taskId || "",
      isLog: locationParams?.isLog || false,
      name: decodeURIComponent(locationParams.name),
    };

    if (flowInfoData.value.isLog) {
      flowStore.updateDisableForm(true);
      scheduleFlowView(flowInfoData.value.instId, flowInfoData.value.taskId, 1000);
    }
  }
};

let flowInitialized = false;
const initFlow = () => {
  if (flowInitialized) return;
  flowInitialized = true;
  lfRef.value = lf;
  loadFlowData(flowInfoData.value.itemId, lf);

  lf.on("edge:add", ({ data }) => validEdge(data));
  lf.on("history:change", () => {
    if (flowStore.disableForm) return;
    const flowData = lf.getGraphData();
    if (JSON.stringify(oldFlowData) !== JSON.stringify(flowData)) {
      flowState.value = "updateData";
    }
  });
};

const groupSelectionPending = ref(false);

const createGroup = (selectedNodes) => {
  const nodes = selectedNodes.filter((item) => (
    !["selectArea", "loop"].includes(item.type)
    && !lf.getNodeModelById(item.id)?.parentId
  ));
  if (nodes.length < 2) {
    ElMessage.warning("请至少选择两个未分组节点");
    groupSelectionPending.value = false;
    return;
  }

  const INSET = { left: 52, top: 120, right: 52, bottom: 52 };
  let minX = Infinity;
  let minY = Infinity;
  let maxX = -Infinity;
  let maxY = -Infinity;
  const children = [];

  nodes.forEach((item) => {
    const model = lf.getNodeModelById(item.id);
    const width = model?.width || 372;
    const height = model?.height || 180;
    const left = Number.isFinite(model?.left) ? model.left : item.x - width / 2;
    const top = Number.isFinite(model?.top) ? model.top : item.y - height / 2;
    minX = Math.min(minX, left);
    minY = Math.min(minY, top);
    maxX = Math.max(maxX, left + width);
    maxY = Math.max(maxY, top + height);
    children.push(item.id);
  });

  const width = maxX - minX + INSET.left + INSET.right;
  const height = maxY - minY + INSET.top + INSET.bottom;
  lf.groupNodes({
    type: "selectArea",
    x: minX - INSET.left + width / 2,
    y: minY - INSET.top + height / 2,
    properties: {
      name: "分组",
      action: "NONE",
      width,
      height,
    },
  }, children);
  lf.clearSelectElements();
  groupSelectionPending.value = false;
};

const group = () => {
  if (groupSelectionPending.value) {
    groupSelectionPending.value = false;
    lf.clearSelectElements();
    return;
  }
  const { nodes } = lf.getSelectElements();
  if (nodes.length) createGroup(nodes);
  else groupSelectionPending.value = true;
};

const handleSelectionEnd = () => {
  if (!groupSelectionPending.value) return;
  createGroup(lf.getSelectElements().nodes);
};

const treeData = ref(null);
const defaultProps = {
  children: "children",
  label: "name",
};
const showNodeRelationship = () => {
  const { nodes } = lf.getGraphData();
  const outputData = convertToTree(nodes);
  treeData.value = outputData;
};

const handleTreeNodeClick = (node) => {
  lf.focusOn({
    id: node.id,
  });
  lf.selectElementById(node.id);
};

const fitView = () => {
  lf.fitView();
};

const nodeName = ref("");
const visible = ref(false);
const formData = reactive({
  nodeParams: [],
});
const rules = reactive({});
const executeForm = ref();
const nodeAction = ref("");

const singleNodeExecution = (e) => {
  const { name, nodeType, nodeParams, action } = e.detail;
  console.log('nodeParams', nodeParams)
  nodeName.value = name;
  visible.value = true;
  nodeAction.value = action;
  if (nodeType === "EDGE") {
    formData.nodeParams = [
      {
        disabled: true,
        input: "",
        name: "terminalId",
        type: "input",
      },
      ...nodeParams,
    ];
  } else {
    formData.nodeParams = nodeParams;
  }
};

const execute = async () => {
  const result = await executeForm.value.validate();
  if (result) {
    const obj = {};
    formData.nodeParams.forEach((item) => {
      obj[item.name] = item.input;
    });
    const res = await flowAction({
      action: nodeAction.value,
      payload: obj,
    });
    if (res.code === 200) {
      ElMessage.success("执行成功");
    } else {
      ElMessage.error(res.msg);
    }
  }
};

const showParamsDrawer = ref(false);
const paramsDrawerData = ref({});

const clearRun = () => {
  const { nodes } = lf.getGraphData();
  nodes.forEach((item) => {
    const node = lf.getNodeModelById(item.id);
    if (node.closePopover) {
      node.closePopover();
    }
  });
  hasRunResult.value = false;
  totalRunningTime.value = 0;
}

const NODE_PASTE_OFFSET = 40;
const NON_COPYABLE_NODE_TYPES = new Set(["start", "end", "selectArea"]);
let copiedNodes = [];
let pasteCount = 0;

const isTextEditing = (event) => {
  const element = event.target || document.activeElement;
  return ["INPUT", "TEXTAREA", "SELECT"].includes(element?.tagName)
    || element?.isContentEditable;
};

const copySelectedNodes = (event) => {
  const nodes = lf.getSelectElements().nodes.filter(
    (node) => !NON_COPYABLE_NODE_TYPES.has(node.type),
  );
  if (!nodes.length) return;

  event.preventDefault();
  copiedNodes = JSON.parse(JSON.stringify(nodes));
  pasteCount = 0;
};

const pasteCopiedNodes = (event) => {
  if (!copiedNodes.length || flowStore.disableForm) return;

  event.preventDefault();
  pasteCount += 1;
  const offset = NODE_PASTE_OFFSET * pasteCount;
  const pastedNodes = copiedNodes.map((sourceNode) => {
    const properties = JSON.parse(JSON.stringify(sourceNode.properties || {}));
    const parentId = properties.parentId;
    delete properties.parentId;

    const newNode = lf.addNode({
      type: sourceNode.type,
      x: Number(sourceNode.x || 0) + offset,
      y: Number(sourceNode.y || 0) + offset,
      properties,
    });

    if (sourceNode.type === "branch" && Array.isArray(properties.anchor)) {
      const oldElseId = `${sourceNode.id}_else`;
      const newElseId = `${newNode.id}_else`;
      const anchors = properties.anchor.map((anchor) => (
        anchor.id === oldElseId ? { ...anchor, id: newElseId } : anchor
      ));
      lf.setProperties(newNode.id, { ...properties, anchor: anchors });
    }

    if (parentId && lf.getNodeModelById(parentId)) {
      lf.addToGroup(parentId, newNode.id, { x: newNode.x, y: newNode.y });
    }
    return newNode;
  });

  lf.clearSelectElements();
  if (pastedNodes.length === 1) lf.selectElementById(pastedNodes[0].id);
};

const handleKeyboardShortcut = (event) => {
  if (isTextEditing(event)) return;

  const isModifierKey = event.ctrlKey || event.metaKey;
  const key = event.key.toLowerCase();
  if (isModifierKey && key === "c") {
    if (!flowStore.disableForm) copySelectedNodes(event);
    return;
  }
  if (isModifierKey && key === "v") {
    pasteCopiedNodes(event);
    return;
  }

  if (isModifierKey && key === "z") {
    if (flowStore.disableForm) return;
    event.preventDefault();
    if (event.shiftKey) lf.redo();
    else lf.undo();
    return;
  }

  if (!["Backspace", "Delete"].includes(event.key) || flowStore.disableForm) return;

  const { nodes, edges } = lf.getSelectElements();
  if (nodes.some((node) => ["start", "end"].includes(node.type))) {
    lf.clearSelectElements();
    return;
  }
  if (nodes.length) {
    event.preventDefault();
    nodes.forEach((node) => {
      lf.deleteNode(node.id);
      emitter.emit("deleteNode", node.id);
    });
    return;
  }
  if (edges.length) {
    event.preventDefault();
    edges.forEach((edge) => lf.deleteEdge(edge.id));
  }
};

const nodeErrorList = ref([]);

onMounted(() => {
  justLoadFlow();
  initFlow();
  document.addEventListener("singleNodeExecution", singleNodeExecution);
  registerBeforeUnload();
  window.addEventListener("keydown", handleKeyboardShortcut);

  onFlowEvent("openParamsDrawer", (node) => {
    showParamsDrawer.value = true;
    paramsDrawerData.value = node;
  });

  onFlowEvent("throwAnError", (data) => {
    // 处理错误抛出逻辑
    const { id, hasError, type } = data;
    let element
    if (type === 'loop') {
      const tt = document.getElementsByClassName(`group__container ${id}`)[0];
      if (tt?.parentElement) {
        element = tt.parentElement;
      } else {
        element = tt
      }
    } else {
      const tt = document.getElementsByClassName(`node__container ${id}`)[0]
      if (tt?.parentElement) {
        element = tt.parentElement;
      } else {
        element = tt
      }
    }
    if (hasError) {
      if (!nodeErrorList.value.includes(id)) {
        nodeErrorList.value.push(id);
        if (element) element.style.background = "#ff00001a";
      }
    } else {
      nodeErrorList.value = nodeErrorList.value.filter((item) => item !== id);
      if (element) element.style.background = "#fff";
    }
  });

  onFlowEvent("deleteNode", (nodeId) => {
    const index = nodeErrorList.value.findIndex((item) => item === nodeId);
    if (index !== -1) {
      nodeErrorList.value.splice(index, 1);
    }
  })
});

onUnmounted(() => {
  flowDisposed = true;
  clearFlowPollTimer();
  loopChildDragContexts.clear();
  if (window.lf === lf) delete window.lf;

  window.removeEventListener("beforeunload", handleBeforeUnload);
  window.removeEventListener("keydown", handleKeyboardShortcut);
  document.removeEventListener("singleNodeExecution", singleNodeExecution);
});
</script>
<style lang="scss" scoped>
$flow-canvas-cursor: url("data:image/svg+xml,%3Csvg%20xmlns=%27http://www.w3.org/2000/svg%27%20width=%2724%27%20height=%2724%27%20viewBox=%270%200%2024%2024%27%3E%3Cpath%20d=%27M4%202.5v16l4.2-4%203.1%207.1%203.4-1.5-3.1-7.1h5.8z%27%20fill=%27%23fff%27%20stroke=%27%231f2937%27%20stroke-width=%271.8%27%20stroke-linejoin=%27round%27/%3E%3C/svg%3E") 4 3, default;

.page {
  width: 100%;
  height: 100%;
  overflow: hidden;

  .page__container {
    width: 100%;
    height: 100%;
    position: relative;

    .el-header {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 0 16px;
      border-bottom: 1px solid #e3e7ee;
      background: #fff;
      gap: 18px;

      .flow-heading {
        display: flex;
        min-width: 0;
        align-items: center;
        gap: 10px;
      }

      .flow-heading__name {
        overflow: hidden;
        max-width: 320px;
        color: #1f2937;
        font-size: 16px;
        font-weight: 650;
        text-overflow: ellipsis;
        white-space: nowrap;
      }

      .flow-status {
        display: flex;
        height: 24px;
        align-items: center;
        gap: 6px;
        padding: 0 8px;
        border: 1px solid #dfe4ec;
        border-radius: 5px;
        background: #f8fafc;
        color: #687386;
        font-size: 11px;
        white-space: nowrap;
      }

      .flow-status__dot {
        width: 6px;
        height: 6px;
        border-radius: 50%;
        background: currentColor;
      }

      .flow-status--release,
      .flow-status--testRunFinish {
        border-color: #c8ead5;
        background: #effaf3;
        color: #1e7d49;
      }

      .flow-status--testRunning,
      .flow-status--updateData {
        border-color: #cbdcf8;
        background: #f0f6ff;
        color: #2f6fca;
      }

      .flow-status--testRunError,
      .flow-status--stop {
        border-color: #f1cecb;
        background: #fff3f2;
        color: #c33b32;
      }

      .flow-status--pause {
        border-color: #ead9b8;
        background: #fff9eb;
        color: #9a6700;
      }

      .btn__container {
        display: flex;
        min-width: 0;
        align-items: center;
        gap: 10px;

        .btn {
          display: flex;
          align-items: center;
          gap: 8px;

          .el-button + .el-button { margin-left: 0; }
        }

        .publish-actions {
          padding-left: 10px;
          border-left: 1px solid #e4e8ef;
        }
      }
    }

    .container {
      height: calc(100% - 60px);
    }

    .el-main {
      padding: 0;
      position: relative;
      overflow: hidden;
    }

    .el-aside {
      margin: 0;
      width: 272px;
      padding: 0;
      border-right: 1px solid #e3e7ee;
      background: #f8fafc;
    }

    .canvas-toolbar {
      display: flex;
      position: absolute;
      top: 14px;
      left: 14px;
      z-index: 20;
      align-items: center;
      padding: 4px;
      border: 1px solid #dfe4ec;
      border-radius: 7px;
      background: rgba(255, 255, 255, 0.96);
      box-shadow: 0 6px 18px rgba(31, 41, 55, 0.1);

      .el-button {
        width: 30px;
        height: 30px;
        padding: 0;
        border: 0;
        border-radius: 5px;
        background: transparent;
        color: #566174;
      }

      .el-button + .el-button { margin-left: 2px; }

      .el-button:hover {
        background: #f1f5f9;
        color: #1f2937;
      }
    }

    .canvas-toolbar__divider {
      width: 1px;
      height: 18px;
      margin: 0 4px;
      background: #e1e6ed;
    }

    .flow__container {
      width: 100%;
      height: 100%;
      cursor: $flow-canvas-cursor;

      :deep(.vue-flow__node) {
        border: 0;
        background: transparent;
        cursor: $flow-canvas-cursor;
      }

      :deep(.vue-flow__node .flow-node-shell),
      :deep(.vue-flow__node .flow-node-content),
      :deep(.vue-flow__node .node__container),
      :deep(.vue-flow__node .right),
      :deep(.vue-flow__node .editIcon),
      :deep(.vue-flow__node .resultText) {
        cursor: $flow-canvas-cursor;
      }

      :deep(.vue-flow__edge-path),
      :deep(.vue-flow__connection-path) {
        stroke: #ff6b35;
        stroke-width: 2;
      }

      :deep(.vue-flow__pane) {
        cursor: $flow-canvas-cursor;
      }

      :deep(.vue-flow__pane.dragging) {
        cursor: $flow-canvas-cursor;
      }

      :deep(.vue-flow__handle) {
        cursor: crosshair;
      }

      :deep(.vue-flow__edge) {
        cursor: pointer;
      }

      :deep(.vue-flow__edge-interaction) {
        cursor: pointer;
        pointer-events: stroke;
      }

      :deep(.vue-flow__edge.selected .vue-flow__edge-path) {
        stroke: #1677ff;
        stroke-width: 4;
        filter: drop-shadow(0 0 3px rgba(22, 119, 255, 0.45));
      }

      :deep(.vue-flow__controls) {
        overflow: hidden;
        border: 1px solid #dfe4ec;
        border-radius: 6px;
        box-shadow: 0 6px 18px rgba(20, 33, 61, 0.12);
      }
    }
  }
}
</style>
<style lang="scss">
.node-relationship-network-popover {
  position: relative;

  .title {
    font-size: 16px;
    font-weight: bold;
    text-align: center;
  }

  .fit {
    position: absolute;
    right: 20px;
    top: 10px;
  }

  .el-tree {
    margin-top: 20px;
    max-height: 200px;
    overflow-y: auto;
  }
}

.el-dialog {
  .el-input {
    width: 200px;
  }

  .el-select {
    width: 200px;
  }

  .el-textarea {
    width: 200px;
  }
}
</style>
