<template>
  <div class="page">
    <el-container class="page__container">
      <el-header v-if="!flowInfoData.isLog">
        <div>
          <div>{{ flowInfoData.name }}</div>
          <div>{{ stateMap[flowState] }}</div>
        </div>
        <div class="btn__container">
          <div class="btn">
            <el-tag style="margin-right: 12px;" v-if="['testRunError', 'testRunFinish'].includes(flowState)" :type="flowState === 'testRunFinish' ? 'success' : 'danger'">
              {{ totalRunningTime }}ms
            </el-tag>
            <el-button v-if="['testRunError', 'testRunFinish', 'stop'].includes(flowState)" @click="clearRun">清除上一次运行结果</el-button>
            <el-button @click="group">分组</el-button>
            <el-button v-if="flowState !== 'testRunning'" @click="testRun">试运行</el-button>
            <el-button v-if="flowState === 'testRunning'" @click="flowPauseFn">暂停</el-button>
            <el-button v-if="flowState === 'pause'" @click="flowResumeFn">恢复</el-button>
            <el-button v-if="['testRunning', 'pause'].includes(flowState)" @click="flowStopFn">终止</el-button>
          </div>
          <div class="btn">
            <el-tooltip
              class="box-item"
              effect="dark"
              content="试运行成功后才可以发布"
              placement="top"
              v-if="flowState !== 'testRunFinish'"
            >
              <el-button type="primary" :disabled="true">发布</el-button>
            </el-tooltip>
            <el-button v-else @click="deployFlow" type="primary">发布</el-button>

            <el-popover
              :width="260"
              @show="showNodeRelationship"
              popper-class="node-relationship-network-popover"
            >
              <template #reference>
                <el-button style="margin-right: 16px"
                  ><el-icon><Location /></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="flow__container"
            ref="flowContainerRef"
            @dragover="dragover"
            @drop="drop"
          ></div>
        </el-main>
      </el-container>
    </el-container>

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

    <ParamsDrawer
      :drawer="showParamsDrawer"
      :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"
              :controls="false"
              :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 { ref, onMounted, onUnmounted, nextTick } from "vue";
import LogicFlow from "@logicflow/core";
import "@logicflow/core/es/index.css";
import "@logicflow/core/lib/style/index.css";
import "@logicflow/extension/lib/style/index.css";
import { lfConfig, registerCustomizeNode } from "./config";
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 { recursiveFilter, newEdge, convertToTree } from "@/utils/flow";
import {
  flowDeploy,
  flowView,
  flowPause,
  flowResume,
  flowStop,
  flowAction,
} from "@/api/device/flow";
import { getDetect } from "@/api/test/detect";
import { Location } from "@element-plus/icons-vue";

const flowContainerRef = ref(null);
const lfRef = ref(null);

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

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

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

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 flowResumeFn = async () => {
  const res = await flowResume(instId.value);
  if (res.code === 200) {
    flowState.value = "testRunning";
    loopFlowView(instId.value);
    ElMessage.success(res.msg);
  }
};

const flowStopFn = async () => {
  const res = await flowStop(instId.value);
  if (res.code === 200) {
    flowState.value = "stop";
    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 validEdge = (data) => {
  const sourceAnchorArr = lf.getNodeModelById(data.sourceNodeId).anchors;
  const [sourceName] = sourceAnchorArr
    .filter((item) => {
      return item.id === data.sourceAnchorId;
    })
    .map((item) => {
      return item.name;
    });

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

  if (sourceName === "right" && targetName === "left") {
    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);
    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;

  instId.value = value.instId;
  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 (res.code === 200) {
    const arr = res.data || [];
    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)
    ) {
      setTimeout(() => {
        loopFlowView(instId);
      }, 2000);
    } 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);
      const selectArea = [];
      flowData.nodes.forEach((item) => {
        if (item.type === "selectArea") {
          selectArea.push(...item.children);
        }
      });

      lf.render(flowData);

      nextTick(() => {
        selectArea.forEach((item) => {
          lf.getNodeModelById(item).draggable = false;
        });
        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);
      setTimeout(() => {
        loopFlowView(flowInfoData.value.instId, flowInfoData.value.taskId);
      }, 1000);
    }
  }
};

const initFlow = () => {
  if (flowContainerRef.value) {
    const lf = new LogicFlow({
      container: flowContainerRef.value,
      ...lfConfig,
      zoomOptions: {
        initScale: 0.5, // 初始缩放 50%
        minScale: 0.1, // 最小缩放 10%
        maxScale: 3, // 最大缩放 300%
      },
      keyboard: {
        enabled: true,
        shortcuts: [
          {
            keys: ["backspace", "delete"], // 覆盖删除键
            callback: () => {
              const activeElem = document.activeElement;
              if (["INPUT", "TEXTAREA"].includes(activeElem.tagName)) {
                return false
              }

              const { nodes, edges } = lf.getSelectElements();
              if (nodes.length > 0) {
                // 检查是否存在受保护节点
                const hasProtectedNode = nodes.some((node) =>
                  ["start", "end"].includes(node.type)
                );

                if (hasProtectedNode) {
                  lf.selectElementById(null); // 取消选中
                  return false; // 阻断删除
                }

                // 默认删除逻辑
                nodes.forEach((node) => {
                  if (node.type === "selectArea") {
                    lf.getNodeModelById(node.id).children = [];
                    node.children.forEach((item) => {
                      lf.getNodeModelById(item).draggable = true;
                    });
                  }

                  lf.deleteNode(node.id);
                  // 删除节点所有的边
                  lf.deleteEdgeByNodeId(node.id);
                });
                return false;
              }
              if (nodes.length === 0 && edges.length > 0) {
                edges.forEach((node) => {
                  // 删除边
                  lf.deleteEdge(node.id);
                });
              }
            },
          },
          {
            keys: ["ctrl+z"], // 自定义快捷键
            callback: () => {
              return true;
            },
          },
          {
            keys: ["ctrl+shift+z"],
            callback: () => true,
          },
        ],
      },
    });

    registerCustomizeNode(lf);

    loadFlowData(flowInfoData.value.itemId, lf);

    lf.on("edge:add", ({ data }) => {
      validEdge(data);
    });

    // 监听历史记录变化
    lf.on("history:change", ({ data }) => {
      if (flowStore.disableForm) {
        return;
      }
      const flowData = lf.getGraphData();
      if (JSON.stringify(oldFlowData) === JSON.stringify(flowData)) {
        return;
      }
      flowState.value = "updateData";
    });

    // 监听子节点拖动事件
    lf.on("node:mousemove", ({ data, e }) => {
      const { nodes } = lf.getGraphData();
      const arr = recursiveFilter(nodes, data.id, "loop");
      arr.forEach((item) => {
        const node = lf.getNodeModelById(item.id);
        node.updateSize();
      });
      operationEdge(arr);
    });

    lf.on("node:mouseleave", ({ data }) => {
      const node = lf.getNodeModelById(data.id);
      if (node.setCustomProperties) {
        node.setCustomProperties();
      }
    });

    lf.on("node:dbclick", ({ data, e }) => {
      if (
        ["selectArea", "branch", "stopLoop", "subStart", "subEnd", "end"].includes(data.type)
      ) {
        return;
      }
      showParamsDrawer.value = true;
      paramsDrawerData.value = data;
    });

    lfRef.value = lf;
    window.lf = lf;
  }
};

const group = () => { 
  const PADDING = 200;
  lf.extension.selectionSelect.openSelectionSelect();
  lf.once("selection:selected", (data) => {
    let minX = Infinity;
    let minY = Infinity;
    let maxX = -Infinity;
    let maxY = -Infinity;

    let children = [];
    data.elements.forEach((item) => {
      if (item.BaseType === "node") {
        minX = Math.min(minX, item.x - item.width / 2);
        minY = Math.min(minY, item.y - item.height / 2);
        maxX = Math.max(maxX, item.x + item.width / 2);
        maxY = Math.max(maxY, item.y + item.height / 2);
        children.push(item.id);
        lf.getNodeModelById(item.id).draggable = false;
      }
    });
    if (children.length === 0) {
      lf.clearSelectElements();
      lf.extension.selectionSelect.closeSelectionSelect();
      return;
    }
    let width = maxX - minX + PADDING;
    let height = maxY - minY + PADDING;

    lf.addNode({
      type: "selectArea",
      x: minX + (maxX - minX) / 2,
      y: minY + (maxY - minY) / 2,
      properties: {
        width,
        height,
      },
      children,
    });
    lf.clearSelectElements();
    lf.extension.selectionSelect.closeSelectionSelect();
  });
};

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;
  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();
    }
  });
}

const nodeErrorList = ref([]);

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

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

  emitter.on("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);
        element.style.background = "#ff00001a";
      }
    } else {
      nodeErrorList.value = nodeErrorList.value.filter((item) => item !== id);
      element.style.background = "#fff";
    }
  });

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

const operationEdge = (arr) => {
  arr.forEach((item) => {
    newEdge(item.id);
  });
};

onUnmounted(() => {
  emitter.off("changeNodeState");
  emitter.off("openParamsDrawer");
  emitter.off("throwAnError");
  emitter.off("deleteNode");

  window.removeEventListener("beforeunload", handleBeforeUnload);
  document.removeEventListener("singleNodeExecution", singleNodeExecution);
});
</script>
<style lang="scss" scoped>
.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;
      border-bottom: 1px solid #ddd;

      .btn__container {
        display: flex;

        .btn {
          margin-right: 12px;
        }
      }
    }

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

    .el-main {
      padding: 0;
    }

    .el-aside {
      margin: 0;
      width: 260px;
      padding: 0;
    }

    .flow__container {
      width: 100%;
      height: 100%;

      :deep(.lf-base) {
        foreignObject {
          overflow: visible !important;
        }

        .lf-node-content {
          .lf-node-anchor {
            display: block !important; /* 强制显示 */
            opacity: 1 !important; /* 取消透明度 */
          }
        }

        .lf-node-selected {
          .node__box {
            border-color: #8dc99d;
          }

          .node__container {
            border-color: #8dc99d;
            // pointer-events: none;
          }

          .group__container {
            border-color: #8dc99d;
            // pointer-events: none;
          }
        }
      }
    }
  }
}
</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>
